diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b13ba136..15a6953e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,40 @@ on: pull_request: jobs: + convex-contract: + runs-on: ubuntu-latest + env: + CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_PREVIEW_DEPLOY_KEY }} + CONVEX_PREVIEW_NAME: typed-wrapper-contract-${{ github.run_id }}-${{ github.run_attempt }} + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - name: Require Convex preview deploy key + shell: bash + run: | + if [[ -z "$CONVEX_DEPLOY_KEY" ]]; then + echo "Add the CONVEX_PREVIEW_DEPLOY_KEY repository secret so CI can create an isolated contract deployment." + exit 1 + fi + + - name: Install Convex dependencies + run: npm ci + + - name: Deploy isolated Convex contract + run: npx convex deploy --preview-create "$CONVEX_PREVIEW_NAME" --typecheck enable + + - name: Compare fresh scrubbed contract + run: | + npm run snapshot:convex-contract:check + npm run audit:convex-contract + git diff --exit-code -- convex/function_spec.json convex/error_codes.json convex/_generated + validate: runs-on: windows-latest env: @@ -15,6 +49,10 @@ jobs: steps: - uses: actions/checkout@v4 + with: + # The contract gate records the merge base with origin/icarus-cloud. + # Fetch all branch history so that ref exists on PR and push runs. + fetch-depth: 0 - uses: actions/setup-node@v4 with: @@ -68,6 +106,38 @@ jobs: shell: pwsh run: fvm flutter pub get + - name: Validate Convex Client Contract Gate + shell: pwsh + working-directory: tool/convex_client_gauntlet + run: | + fvm dart pub get + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + fvm dart test + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + # Analyze this package only. The nested runtime fixtures are separate + # Flutter packages and are validated by the Linux runtime job after + # their own dependencies have been resolved. + fvm dart analyze bin lib test + + - name: Validate Icarus Convex Generator + shell: pwsh + working-directory: tool/icarus_convex_codegen + run: | + fvm dart pub get + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + fvm dart analyze + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + fvm dart test + + - name: Reject Generated Client Drift + shell: pwsh + run: | + fvm dart run tool/icarus_convex_codegen/bin/generate.dart + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + fvm dart run tool/icarus_convex_codegen/bin/generate.dart + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + git diff --exit-code -- lib/collab/generated + - name: Analyze shell: pwsh run: fvm flutter analyze --no-fatal-infos @@ -77,3 +147,92 @@ jobs: - name: Run Tests shell: pwsh run: fvm flutter test + - name: Test Native Convex Bridge + shell: pwsh + run: | + cargo test --manifest-path third_party/convex_flutter/rust/Cargo.toml + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + cargo test --manifest-path third_party/convex_rs/Cargo.toml + - name: Build Windows Client + shell: pwsh + run: fvm flutter build windows --no-tree-shake-icons + + linux: + runs-on: ubuntu-latest + env: + FVM_HOME: ${{ github.workspace }}/.fvm_cache + PUB_CACHE: ${{ github.workspace }}/.pub-cache + + steps: + - uses: actions/checkout@v4 + + - uses: dart-lang/setup-dart@v1 + + - name: Install Linux build dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y clang cmake ninja-build pkg-config libgtk-3-dev liblzma-dev + + - name: Cache Pub Packages + uses: actions/cache@v4 + with: + path: .pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.lock') }} + restore-keys: | + ${{ runner.os }}-pub- + + - name: Cache FVM SDK + uses: actions/cache@v4 + with: + path: .fvm_cache + key: ${{ runner.os }}-fvm-${{ hashFiles('.fvmrc') }} + restore-keys: | + ${{ runner.os }}-fvm- + + - name: Add Pub Cache To PATH + shell: bash + run: echo "$PUB_CACHE/bin" >> "$GITHUB_PATH" + + - name: Install FVM and Flutter SDK + shell: bash + run: | + dart pub global activate fvm + fvm install + + - name: Get Dependencies + run: fvm flutter pub get + + - name: Validate Icarus Convex Generator + working-directory: tool/icarus_convex_codegen + run: | + fvm dart pub get + fvm dart analyze + fvm dart test + + - name: Validate Protocol V3 Gauntlet Workload + working-directory: tool/convex_client_gauntlet/runtime + run: | + fvm flutter pub get + fvm dart analyze lib test tool + fvm flutter test test/workload_test.dart + + - name: Reject Generated Client Drift + run: | + fvm dart run tool/icarus_convex_codegen/bin/generate.dart + fvm dart run tool/icarus_convex_codegen/bin/generate.dart + git diff --exit-code -- lib/collab/generated + + - name: Analyze + run: fvm flutter analyze --no-fatal-infos + + - name: Run Tests + run: fvm flutter test + + - name: Test Native Convex Bridge + run: | + cargo test --manifest-path third_party/convex_flutter/rust/Cargo.toml + cargo test --manifest-path third_party/convex_rs/Cargo.toml + + - name: Build Linux Client + run: fvm flutter build linux --no-tree-shake-icons diff --git a/CONTEXT.md b/CONTEXT.md index e951f111..421c727b 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -49,3 +49,14 @@ into lineup groups. Icarus's zip-based strategy interchange format for import/export of whole strategies. Unrelated to video export. _Avoid_: archive (ambiguous with library backups) + +**Op**: +One queued change to cloud data. An op lands when the server accepts it. +Its op ID names that exact change. Changing the intended work creates a new op +with a new ID; retrying the same work keeps the existing ID. +_Avoid_: request, event + +**Outbox record**: +The durable saved form of one queued op and its delivery state, used to recover +unsent cloud work after an app restart. It is not a server payload. +_Avoid_: payload, cached request diff --git a/analysis_options.yaml b/analysis_options.yaml index 12180237..b369a299 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -11,6 +11,9 @@ analyzer: exclude: - build/** - lib/hive/hive_adapters.g.dart + - third_party/** + - tool/convex_client_gauntlet/** + - tool/icarus_convex_codegen/** errors: curly_braces_in_flow_control_structures: ignore include: package:flutter_lints/flutter.yaml diff --git a/convex/_generated/api.d.ts b/convex/_generated/api.d.ts index d1628270..00ff5ebe 100644 --- a/convex/_generated/api.d.ts +++ b/convex/_generated/api.d.ts @@ -15,12 +15,14 @@ import type * as health from "../health.js"; import type * as images from "../images.js"; import type * as invites from "../invites.js"; import type * as lib_auth from "../lib/auth.js"; +import type * as lib_canonicalValues from "../lib/canonicalValues.js"; 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_opTypes from "../lib/opTypes.js"; import type * as lib_payloadValidators from "../lib/payloadValidators.js"; +import type * as lib_publicValidators from "../lib/publicValidators.js"; import type * as lib_r2 from "../lib/r2.js"; import type * as lib_snapshotSerialization from "../lib/snapshotSerialization.js"; import type * as lineups from "../lineups.js"; @@ -47,12 +49,14 @@ declare const fullApi: ApiFromModules<{ images: typeof images; invites: typeof invites; "lib/auth": typeof lib_auth; + "lib/canonicalValues": typeof lib_canonicalValues; "lib/cloudProtocol": typeof lib_cloudProtocol; "lib/entities": typeof lib_entities; "lib/errors": typeof lib_errors; "lib/imageAssets": typeof lib_imageAssets; "lib/opTypes": typeof lib_opTypes; "lib/payloadValidators": typeof lib_payloadValidators; + "lib/publicValidators": typeof lib_publicValidators; "lib/r2": typeof lib_r2; "lib/snapshotSerialization": typeof lib_snapshotSerialization; lineups: typeof lineups; diff --git a/convex/_generated/server.d.ts b/convex/_generated/server.d.ts index bec05e68..f235db4a 100644 --- a/convex/_generated/server.d.ts +++ b/convex/_generated/server.d.ts @@ -21,6 +21,17 @@ import { } from "convex/server"; import type { DataModel } from "./dataModel.js"; +/** + * Typesafe environment variables. + * + * This includes platform-provided env vars and any variables declared in + * `convex.config.ts`. + */ +type Env = { + readonly CONVEX_CLOUD_URL: string; + readonly CONVEX_SITE_URL: string; +}; + /** * Define a query in this Convex app's public API. * @@ -95,6 +106,14 @@ export declare const internalAction: ActionBuilder; */ export declare const httpAction: HttpActionBuilder; +/** + * Typesafe environment variables. + * + * This includes platform-provided env vars and any variables declared in + * `convex.config.ts`. + */ +export declare const env: Env; + /** * A set of services for use within Convex query functions. * diff --git a/convex/_generated/server.js b/convex/_generated/server.js index bf3d25ad..2dbe0dbd 100644 --- a/convex/_generated/server.js +++ b/convex/_generated/server.js @@ -91,3 +91,11 @@ export const internalAction = internalActionGeneric; * @returns The wrapped function. Import this function from `convex/http.js` and route it to hook it up. */ export const httpAction = httpActionGeneric; + +/** + * Typesafe environment variables. + * + * This includes platform-provided env vars and any variables declared in + * `convex.config.ts`. + */ +export const env = process.env; diff --git a/convex/elements.ts b/convex/elements.ts index c91c182d..2c147477 100644 --- a/convex/elements.ts +++ b/convex/elements.ts @@ -3,12 +3,14 @@ import { v } from "convex/values"; import { assertStrategyRole } from "./lib/auth"; import { getPageByPublicId, getStrategyByPublicId } from "./lib/entities"; import { errorWithCode } from "./lib/errors"; +import { elementValidator } from "./lib/publicValidators"; export const listForPage = query({ args: { strategyPublicId: v.string(), pagePublicId: v.string(), }, + returns: v.array(elementValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); @@ -44,6 +46,7 @@ export const listForStrategy = query({ args: { strategyPublicId: v.string(), }, + returns: v.array(elementValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); diff --git a/convex/error_codes.json b/convex/error_codes.json new file mode 100644 index 00000000..55398102 --- /dev/null +++ b/convex/error_codes.json @@ -0,0 +1,36 @@ +[ + "CLIENT_UPGRADE_REQUIRED", + "CONFLICT", + "ELEMENT_STRATEGY_MISMATCH", + "ELEMENT_TYPE_PAYLOAD_KIND_MISMATCH", + "FORBIDDEN", + "INTERNAL_ERROR", + "INVALID_ELEMENT_PAYLOAD_DATA", + "INVALID_ELEMENT_PAYLOAD_KIND", + "INVALID_ELEMENT_PAYLOAD_VERSION", + "INVALID_LINEUP_PAYLOAD_DATA", + "INVALID_LINEUP_PAYLOAD_KIND", + "INVALID_LINEUP_PAYLOAD_VERSION", + "INVALID_OP", + "INVALID_PAGE_CONTENT_COUNT", + "INVALID_PAYLOAD", + "INVITE_EXPIRED", + "INVITE_REVOKED", + "LINEUP_STRATEGY_MISMATCH", + "MISSING_ADD_ELEMENT_ARGS", + "MISSING_ADD_LINEUP_ARGS", + "MISSING_ELEMENT_PAYLOAD", + "MISSING_ENTITY_PUBLIC_ID", + "MISSING_LINEUP_PAYLOAD", + "MISSING_PAGE_ID", + "MISSING_PAGE_PUBLIC_ID", + "NOT_FOUND", + "PAGE_DESCRIPTOR_REQUIRES_PAGE_OP", + "PAGE_SETTINGS_REQUIRE_PAGE_CONTENT", + "PAGE_STRATEGY_MISMATCH", + "R2_OBJECT_KEY_MISMATCH", + "SHARE_LINK_REVOKED", + "UNAUTHENTICATED", + "UNSUPPORTED_OP", + "UPLOAD_INTENT_NOT_FOUND" +] diff --git a/convex/folders.ts b/convex/folders.ts index 7679b46f..1d32f76b 100644 --- a/convex/folders.ts +++ b/convex/folders.ts @@ -13,6 +13,11 @@ import { forbiddenError, invalidOpError, } from "./lib/errors"; +import { + createResultValidator, + folderSummaryValidator, + okResultValidator, +} from "./lib/publicValidators"; type FolderScope = "owned" | "shared" | "all"; type AnyCtx = QueryCtx | MutationCtx; @@ -101,54 +106,6 @@ const folderScopeValidator = v.optional( v.union(v.literal("owned"), v.literal("shared"), v.literal("all")), ); -export const listForParent = query({ - args: { - parentFolderPublicId: v.optional(v.string()), - scope: folderScopeValidator, - }, - handler: async (ctx, args) => { - const user = await requireCurrentUser(ctx); - const scope = args.scope ?? "owned"; - - let parentFolderId: Id<"folders"> | undefined; - if (args.parentFolderPublicId !== undefined) { - const parent = await getFolderByPublicId(ctx, args.parentFolderPublicId); - await assertFolderRole(ctx, parent, "viewer"); - parentFolderId = parent._id; - } - - const accessible = await listAccessibleFoldersForScope( - ctx, - user._id, - scope, - ); - const folderLookup = new Map( - accessible.map(({ folder }) => [folder._id, folder]), - ); - - return accessible - .filter(({ folder }) => folder.parentFolderId === parentFolderId) - .sort((a, b) => a.folder.createdAt - b.folder.createdAt) - .map(({ folder, role }) => ({ - publicId: folder.publicId, - name: folder.name, - iconId: folder.iconId ?? null, - iconCodePoint: folder.iconCodePoint ?? null, - iconFontFamily: folder.iconFontFamily ?? null, - iconFontPackage: folder.iconFontPackage ?? null, - color: folder.color ?? null, - customColorValue: folder.customColorValue ?? null, - parentFolderPublicId: - folder.parentFolderId === undefined - ? null - : (folderLookup.get(folder.parentFolderId)?.publicId ?? null), - createdAt: folder.createdAt, - updatedAt: folder.updatedAt, - role, - })); - }, -}); - export const create = mutation({ args: { publicId: v.string(), @@ -161,6 +118,7 @@ export const create = mutation({ color: v.optional(v.string()), customColorValue: v.optional(v.number()), }, + returns: createResultValidator, handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const now = Date.now(); @@ -181,7 +139,7 @@ export const create = mutation({ .collect(); const existingOwned = existing.find((item) => item.ownerId === user._id); if (existingOwned !== undefined) { - return { ok: true, reused: true }; + return { ok: true, reused: true } as const; } if (existing.length > 0) { throw conflictError(`Folder publicId already exists: ${args.publicId}`); @@ -202,7 +160,7 @@ export const create = mutation({ updatedAt: now, }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -220,6 +178,7 @@ export const update = mutation({ customColorValue: v.optional(v.number()), clearCustomColorValue: v.optional(v.boolean()), }, + returns: okResultValidator, handler: async (ctx, args) => { const folder = await getFolderByPublicId(ctx, args.folderPublicId); const { role } = await assertFolderRole(ctx, folder, "owner"); @@ -270,14 +229,15 @@ export const update = mutation({ } await ctx.db.patch(folder._id, patch); - return { ok: true }; + return { ok: true } as const; }, }); -export const listAll = query({ +export const listTree = query({ args: { scope: folderScopeValidator, }, + returns: v.array(folderSummaryValidator), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const scope = args.scope ?? "all"; @@ -317,6 +277,7 @@ export const move = mutation({ folderPublicId: v.string(), parentFolderPublicId: v.optional(v.string()), }, + returns: okResultValidator, handler: async (ctx, args) => { const folder = await getFolderByPublicId(ctx, args.folderPublicId); const { role } = await assertFolderRole(ctx, folder, "owner"); @@ -340,14 +301,15 @@ export const move = mutation({ updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true } as const; }, }); -export const deleteFolder = mutation({ +const deleteFolder = mutation({ args: { folderPublicId: v.string(), }, + returns: okResultValidator, handler: async (ctx, args) => { const folder = await getFolderByPublicId(ctx, args.folderPublicId); const { role } = await assertFolderRole(ctx, folder, "owner"); @@ -389,7 +351,7 @@ export const deleteFolder = mutation({ } await ctx.db.delete(folder._id); - return { ok: true }; + return { ok: true } as const; }, }); diff --git a/convex/function_spec.json b/convex/function_spec.json new file mode 100644 index 00000000..d917d326 --- /dev/null +++ b/convex/function_spec.json @@ -0,0 +1,166073 @@ +{ + "functions": [ + { + "args": { + "type": "object", + "value": { + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "elements.js:listForPage", + "returns": { + "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 + } + } + }, + { + "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 + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "elements.js:listForStrategy", + "returns": { + "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 + } + } + }, + { + "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 + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "color": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "customColorValue": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "iconCodePoint": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "iconFontFamily": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "iconFontPackage": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "iconId": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "parentFolderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "folders.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": { + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "folders.js:delete", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "scope": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owned" + }, + { + "type": "literal", + "value": "shared" + }, + { + "type": "literal", + "value": "all" + } + ] + }, + "optional": true + } + } + }, + "functionType": "Query", + "identifier": "folders.js:listTree", + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "color": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "customColorValue": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "iconCodePoint": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "iconFontFamily": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "iconFontPackage": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "iconId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "parentFolderPublicId": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "owner" + }, + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "parentFolderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "folders.js:move", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "clearCustomColorValue": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clearIconFontFamily": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "clearIconFontPackage": { + "fieldType": { + "type": "boolean" + }, + "optional": true + }, + "color": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "customColorValue": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "iconCodePoint": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "iconFontFamily": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "iconFontPackage": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "iconId": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "folders.js:update", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Query", + "identifier": "health.js:ping", + "returns": { + "type": "literal", + "value": "ok" + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "height": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "mimeType": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "storageId": { + "fieldType": { + "tableName": "_storage", + "type": "id" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "number" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "images.js:completeLegacyUpload", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "byteSize": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "etag": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "height": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "mimeType": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "objectKey": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "provider": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "convex" + }, + { + "type": "literal", + "value": "r2" + } + ] + }, + "optional": true + }, + "storageId": { + "fieldType": { + "tableName": "_storage", + "type": "id" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "width": { + "fieldType": { + "type": "number" + }, + "optional": true + } + } + }, + "functionType": "Action", + "identifier": "images.js:completeUpload", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "provider": { + "fieldType": { + "type": "literal", + "value": "convex" + }, + "optional": false + } + } + }, + { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + }, + "provider": { + "fieldType": { + "type": "literal", + "value": "r2" + }, + "optional": false + }, + "url": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "byteSize": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "height": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "mimeType": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "objectKey": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadAttemptPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "number" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "images.js:createR2UploadIntent", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Action", + "identifier": "images.js:deleteAssetRef", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "byteSize": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "height": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "mimeType": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "number" + }, + "optional": true + } + } + }, + "functionType": "Action", + "identifier": "images.js:generateUploadUrl", + "returns": { + "type": "object", + "value": { + "expiresAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "maxBytes": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "objectKey": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "provider": { + "fieldType": { + "type": "literal", + "value": "r2" + }, + "optional": false + }, + "requiredHeaders": { + "fieldType": { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "string" + }, + "optional": false + } + }, + "optional": false + }, + "uploadId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadUrl": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:getAssetDeletionTarget", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:getAssetUrl", + "returns": { + "type": "object", + "value": { + "url": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadAttemptPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:getR2UploadIntentForCompletion", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:listForStrategy", + "returns": { + "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 + } + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "limit": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:listPotentiallyStale", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "limit": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "staleBefore": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "images.js:listStaleUploadDeletionTargets", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "assetIds": { + "fieldType": { + "type": "array", + "value": { + "tableName": "imageAssets", + "type": "id" + } + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "images.js:markDeletedAssetRefsForStrategy", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "assetPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "byteSize": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "etag": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "fileExtension": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "height": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "mimeType": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "uploadId": { + "fieldType": { + "tableName": "imageAssets", + "type": "id" + }, + "optional": false + }, + "width": { + "fieldType": { + "type": "number" + }, + "optional": true + } + } + }, + "functionType": "Mutation", + "identifier": "images.js:markR2UploadActive", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "reason": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "uploadId": { + "fieldType": { + "tableName": "imageAssets", + "type": "id" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "images.js:markR2UploadFailed", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "limit": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "staleBefore": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Action", + "identifier": "images.js:sweepStaleUploadsForStrategy", + "returns": null, + "visibility": { + "kind": "internal" + } + }, + { + "args": { + "type": "object", + "value": { + "expiresAt": { + "fieldType": { + "type": "number" + }, + "optional": true + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "invites.js:create", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": true + } + } + }, + "functionType": "Query", + "identifier": "invites.js:get", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expiresAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "hasAccessAlready": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "inviteRole": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "revoked": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "expiresAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "redeemed": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "revokedAt": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "number" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "role": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "literal", + "value": "editor" + }, + { + "type": "literal", + "value": "viewer" + } + ] + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + } + }, + { + "type": "null" + } + ] + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "invites.js:redeem", + "returns": { + "type": "object", + "value": { + "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 + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "invites.js:revoke", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": { + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "lineups.js:listForPage", + "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" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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": { + "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" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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": { + "pageId": { + "fieldType": { + "tableName": "pages", + "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": { + "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 + }, + "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 + }, + "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": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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 + } + } + }, + { + "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 + }, + "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": { + "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 + } + } + }, + { + "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": 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": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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 + }, + "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" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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 + } + } + }, + { + "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 + } + } + }, + { + "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": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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": { + "pagePublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "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" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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 + } + } + }, + { + "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": "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": "lineupGroup" + }, + "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 + }, + "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": { + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "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": { + "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 + }, + "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": { + "expectedRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "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": { + "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": { + "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": { + "token": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Mutation", + "identifier": "shares.js:redeem", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "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": { + "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": { + "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": { + "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": { + "folderPublicId": { + "fieldType": { + "type": "string" + }, + "optional": true + }, + "initialPageIsAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "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": { + "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": { + "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": { + "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 + }, + "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": { + "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 + } + } + }, + { + "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 + }, + "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" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + "optional": false + } + } + ] + } + }, + { + "keys": { + "type": "string" + }, + "type": "record", + "values": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "type": "string" + } + ] + }, + { + "type": "array", + "value": { + "type": "union", + "value": [ + { + "type": "null" + }, + { + "type": "boolean" + }, + { + "type": "number" + }, + { + "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": "lineupGroup" + }, + "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 + }, + "pages": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "contentCreatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "contentRevision": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "contentUpdatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "name": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "publicId": { + "fieldType": { + "type": "string" + }, + "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 + }, + "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": { + "strategyPublicId": { + "fieldType": { + "type": "string" + }, + "optional": false + } + } + }, + "functionType": "Query", + "identifier": "strategy.js:getShell", + "returns": { + "type": "object", + "value": { + "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 + }, + "pages": { + "fieldType": { + "type": "array", + "value": { + "type": "object", + "value": { + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "isAttack": { + "fieldType": { + "type": "boolean" + }, + "optional": false + }, + "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": {} + }, + "functionType": "Mutation", + "identifier": "users.js:ensureCurrentUser", + "returns": { + "type": "object", + "value": { + "ok": { + "fieldType": { + "type": "literal", + "value": true + }, + "optional": false + } + } + }, + "visibility": { + "kind": "public" + } + }, + { + "args": { + "type": "object", + "value": {} + }, + "functionType": "Query", + "identifier": "users.js:me", + "returns": { + "type": "union", + "value": [ + { + "type": "object", + "value": { + "avatarUrl": { + "fieldType": { + "type": "union", + "value": [ + { + "type": "string" + }, + { + "type": "null" + } + ] + }, + "optional": false + }, + "createdAt": { + "fieldType": { + "type": "number" + }, + "optional": false + }, + "displayName": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "externalId": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "id": { + "fieldType": { + "type": "string" + }, + "optional": false + }, + "updatedAt": { + "fieldType": { + "type": "number" + }, + "optional": false + } + } + }, + { + "type": "null" + } + ] + }, + "visibility": { + "kind": "public" + } + } + ] +} diff --git a/convex/health.ts b/convex/health.ts index 5abeedde..d5a37427 100644 --- a/convex/health.ts +++ b/convex/health.ts @@ -1,9 +1,11 @@ // convex/health.ts import { query } from "./_generated/server"; +import { v } from "convex/values"; export const ping = query({ - args: {}, - handler: async () => { - return "ok"; - }, -}); \ No newline at end of file + args: {}, + returns: v.literal("ok"), + handler: async () => { + return "ok" as const; + }, +}); diff --git a/convex/images.ts b/convex/images.ts index e6db4d9c..8a6e85bb 100644 --- a/convex/images.ts +++ b/convex/images.ts @@ -15,6 +15,7 @@ import { } from "./lib/imageAssets"; import { action, + internalAction, internalMutation, internalQuery, query, @@ -42,6 +43,11 @@ import { invalidPayloadError, notFoundError, } from "./lib/errors"; +import { + imageAssetValidator, + imageProviderValidator, + okResultValidator, +} from "./lib/publicValidators"; type AnyCtx = MutationCtx | QueryCtx; @@ -53,7 +59,9 @@ type DeletionTarget = { const maxDeletionBatch = 100; -const providerValidator = v.union(v.literal("convex"), v.literal("r2")); +function createUploadAttemptPublicId(): string { + return crypto.randomUUID(); +} async function collectReferencedAssetIdsForStrategy( ctx: AnyCtx, @@ -165,6 +173,15 @@ export const generateUploadUrl = action({ width: v.optional(v.number()), height: v.optional(v.number()), }, + returns: v.object({ + provider: v.literal("r2"), + uploadId: v.string(), + objectKey: v.string(), + uploadUrl: v.string(), + requiredHeaders: v.record(v.string(), v.string()), + expiresAt: v.number(), + maxBytes: v.number(), + }), handler: async (ctx, args) => { const config = getR2Config(); const validated = validateImageUploadMetadata({ @@ -179,11 +196,17 @@ export const generateUploadUrl = action({ fileExtension: validated.fileExtension, }); - const intent: { uploadId: Id<"imageAssets">; objectKey: string } = + const uploadAttemptPublicId = createUploadAttemptPublicId(); + const intent: { + uploadId: Id<"imageAssets">; + uploadAttemptPublicId: string; + objectKey: string; + } = await ctx.runMutation(internal.images.createR2UploadIntent, { strategyPublicId: args.strategyPublicId, assetPublicId: args.assetPublicId, objectKey, + uploadAttemptPublicId, mimeType: validated.mimeType, fileExtension: validated.fileExtension, byteSize: args.byteSize, @@ -198,7 +221,7 @@ export const generateUploadUrl = action({ return { provider: "r2" as const, - uploadId: intent.uploadId, + uploadId: intent.uploadAttemptPublicId, objectKey: intent.objectKey, uploadUrl: signed.uploadUrl, requiredHeaders: signed.requiredHeaders, @@ -213,6 +236,7 @@ export const createR2UploadIntent = internalMutation({ strategyPublicId: v.string(), assetPublicId: v.string(), objectKey: v.string(), + uploadAttemptPublicId: v.string(), mimeType: v.string(), fileExtension: v.string(), byteSize: v.optional(v.number()), @@ -238,6 +262,7 @@ export const createR2UploadIntent = internalMutation({ strategyId: strategy._id, createdByUserId: user._id, objectKey: args.objectKey, + uploadAttemptPublicId: args.uploadAttemptPublicId, uploadStatus: "pending", fileExtension: args.fileExtension, mimeType: args.mimeType, @@ -248,7 +273,11 @@ export const createR2UploadIntent = internalMutation({ updatedAt: now, }); - return { uploadId, objectKey: args.objectKey }; + return { + uploadId, + uploadAttemptPublicId: args.uploadAttemptPublicId, + objectKey: args.objectKey, + }; }, }); @@ -256,8 +285,8 @@ export const completeUpload = action({ args: { strategyPublicId: v.string(), assetPublicId: v.string(), - provider: v.optional(providerValidator), - uploadId: v.optional(v.id("imageAssets")), + provider: v.optional(imageProviderValidator), + uploadId: v.optional(v.string()), objectKey: v.optional(v.string()), storageId: v.optional(v.id("_storage")), etag: v.optional(v.string()), @@ -267,14 +296,18 @@ export const completeUpload = action({ width: v.optional(v.number()), height: v.optional(v.number()), }, + returns: v.union( + v.object({ ok: v.literal(true), provider: v.literal("convex") }), + v.object({ + ok: v.literal(true), + provider: v.literal("r2"), + url: v.string(), + }), + ), handler: async ( ctx, args, - ): Promise<{ - ok: true; - provider: Provider; - url?: string | null; - }> => { + ) => { if (args.storageId !== undefined || args.provider === "convex") { await ctx.runMutation(internal.images.completeLegacyUpload, { strategyPublicId: args.strategyPublicId, @@ -285,7 +318,7 @@ export const completeUpload = action({ width: args.width, height: args.height, }); - return { ok: true, provider: "convex" }; + return { ok: true, provider: "convex" } as const; } if (args.uploadId === undefined) { @@ -302,7 +335,7 @@ export const completeUpload = action({ } = await ctx.runQuery(internal.images.getR2UploadIntentForCompletion, { strategyPublicId: args.strategyPublicId, assetPublicId: args.assetPublicId, - uploadId: args.uploadId, + uploadAttemptPublicId: args.uploadId, }); if (args.objectKey !== undefined && args.objectKey !== intent.objectKey) { @@ -313,7 +346,7 @@ export const completeUpload = action({ } if (intent.uploadStatus === "active") { return { - ok: true, + ok: true as const, provider: "r2" as const, url: publicR2UrlForObjectKey(intent.objectKey), }; @@ -371,7 +404,7 @@ export const completeUpload = action({ } return { - ok: true, + ok: true as const, provider: "r2" as const, url: publicR2UrlForObjectKey(intent.objectKey), }; @@ -382,13 +415,18 @@ export const getR2UploadIntentForCompletion = internalQuery({ args: { strategyPublicId: v.string(), assetPublicId: v.string(), - uploadId: v.id("imageAssets"), + uploadAttemptPublicId: v.string(), }, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); - const asset = await ctx.db.get(args.uploadId); + const asset = await ctx.db + .query("imageAssets") + .withIndex("by_uploadAttemptPublicId", (q) => + q.eq("uploadAttemptPublicId", args.uploadAttemptPublicId), + ) + .unique(); if ( asset === null || asset.strategyId !== strategy._id || @@ -492,7 +530,7 @@ export const markR2UploadFailed = internalMutation({ uploadStatus: "failed", updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -574,6 +612,7 @@ export const listForStrategy = query({ args: { strategyPublicId: v.string(), }, + returns: v.array(imageAssetValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); @@ -601,6 +640,7 @@ export const getAssetUrl = query({ strategyPublicId: v.string(), assetPublicId: v.string(), }, + returns: v.object({ url: v.union(v.string(), v.null()) }), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); @@ -638,6 +678,7 @@ export const deleteAssetRef = action({ strategyPublicId: v.string(), assetPublicId: v.string(), }, + returns: okResultValidator, handler: async (ctx, args) => { const target: DeletionTarget = await ctx.runQuery( internal.images.getAssetDeletionTarget, @@ -652,7 +693,7 @@ export const deleteAssetRef = action({ strategyPublicId: args.strategyPublicId, assetIds: [target.assetId], }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -711,7 +752,7 @@ export const markDeletedAssetRefsForStrategy = internalMutation({ }, }); -export const listPotentiallyStale = query({ +export const listPotentiallyStale = internalQuery({ args: { strategyPublicId: v.string(), limit: v.optional(v.number()), @@ -748,7 +789,7 @@ export const listPotentiallyStale = query({ }, }); -export const sweepStaleUploadsForStrategy = action({ +export const sweepStaleUploadsForStrategy = internalAction({ args: { strategyPublicId: v.string(), staleBefore: v.number(), diff --git a/convex/invites.ts b/convex/invites.ts index f73e8e58..618d8865 100644 --- a/convex/invites.ts +++ b/convex/invites.ts @@ -14,12 +14,41 @@ import { notFoundError, errorWithCode, } from "./lib/errors"; +import { + accessRoleValidator, + collaboratorRoleValidator, + okResultValidator, +} from "./lib/publicValidators"; + +const invitePreviewValidator = v.object({ + token: v.string(), + strategyPublicId: v.string(), + inviteRole: collaboratorRoleValidator, + hasAccessAlready: v.boolean(), + revoked: v.boolean(), + expiresAt: v.union(v.number(), v.null()), + createdAt: v.number(), +}); + +const inviteSummaryValidator = v.object({ + token: v.string(), + role: collaboratorRoleValidator, + createdAt: v.number(), + expiresAt: v.union(v.number(), v.null()), + revokedAt: v.union(v.number(), v.null()), + redeemed: v.boolean(), +}); export const get = query({ args: { token: v.optional(v.string()), strategyPublicId: v.optional(v.string()), }, + returns: v.union( + invitePreviewValidator, + v.array(inviteSummaryValidator), + v.null(), + ), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); @@ -66,7 +95,7 @@ export const get = query({ createdAt: invite.createdAt, expiresAt: invite.expiresAt ?? null, revokedAt: invite.revokedAt ?? null, - redeemedByUserId: invite.redeemedByUserId ?? null, + redeemed: invite.redeemedByUserId !== undefined, })); } @@ -81,6 +110,7 @@ export const create = mutation({ role: v.union(v.literal("editor"), v.literal("viewer")), expiresAt: v.optional(v.number()), }, + returns: okResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); const { user, role } = await assertStrategyRole(ctx, strategy, "owner"); @@ -99,7 +129,7 @@ export const create = mutation({ updatedAt: now, }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -107,6 +137,11 @@ export const redeem = mutation({ args: { token: v.string(), }, + returns: v.object({ + ok: v.literal(true), + strategyPublicId: v.string(), + role: accessRoleValidator, + }), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const invite = await ctx.db @@ -172,7 +207,7 @@ export const redeem = mutation({ ok: true, strategyPublicId: strategy.publicId, role: strategy.ownerId === user._id ? "owner" : redeemedRole, - }; + } as const; }, }); @@ -181,6 +216,7 @@ export const revoke = mutation({ strategyPublicId: v.string(), token: v.string(), }, + returns: okResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); const { role } = await assertStrategyRole(ctx, strategy, "owner"); @@ -202,6 +238,6 @@ export const revoke = mutation({ updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true } as const; }, }); diff --git a/convex/lib/cloudProtocol.ts b/convex/lib/cloudProtocol.ts index 852e9b62..453ef977 100644 --- a/convex/lib/cloudProtocol.ts +++ b/convex/lib/cloudProtocol.ts @@ -1,10 +1,9 @@ import { clientUpgradeRequiredError } from "./errors"; -export const CURRENT_CLOUD_PROTOCOL_VERSION = 2; -export const MIN_CLOUD_PROTOCOL_VERSION = 2; +export const CURRENT_CLOUD_PROTOCOL_VERSION = 3; export function assertSupportedCloudProtocol(clientProtocolVersion: number): void { - if (clientProtocolVersion < MIN_CLOUD_PROTOCOL_VERSION) { + if (clientProtocolVersion !== CURRENT_CLOUD_PROTOCOL_VERSION) { throw clientUpgradeRequiredError(); } } diff --git a/convex/lib/errors.ts b/convex/lib/errors.ts index 933f531d..8f167752 100644 --- a/convex/lib/errors.ts +++ b/convex/lib/errors.ts @@ -1,40 +1,43 @@ import { ConvexError } from 'convex/values'; -export type ErrorCode = - | "CLIENT_UPGRADE_REQUIRED" - | "CONFLICT" - | "ELEMENT_STRATEGY_MISMATCH" - | "ELEMENT_TYPE_PAYLOAD_KIND_MISMATCH" - | "FORBIDDEN" - | "INTERNAL_ERROR" - | "INVALID_ELEMENT_PAYLOAD_DATA" - | "INVALID_ELEMENT_PAYLOAD_KIND" - | "INVALID_ELEMENT_PAYLOAD_VERSION" - | "INVALID_LINEUP_PAYLOAD_DATA" - | "INVALID_LINEUP_PAYLOAD_KIND" - | "INVALID_LINEUP_PAYLOAD_VERSION" - | "INVALID_OP" - | "INVALID_PAYLOAD" - | "INVITE_EXPIRED" - | "INVITE_REVOKED" - | "LINEUP_STRATEGY_MISMATCH" - | "MISSING_ADD_ELEMENT_ARGS" - | "MISSING_ADD_LINEUP_ARGS" - | "MISSING_ELEMENT_PAYLOAD" - | "MISSING_ENTITY_PUBLIC_ID" - | "MISSING_LINEUP_PAYLOAD" - | "MISSING_PAGE_ID" - | "MISSING_PAGE_PUBLIC_ID" - | "NOT_FOUND" - | "PAGE_STRATEGY_MISMATCH" - | "INVALID_PAGE_CONTENT_COUNT" - | "PAGE_DESCRIPTOR_REQUIRES_PAGE_OP" - | "PAGE_SETTINGS_REQUIRE_PAGE_CONTENT" - | "R2_OBJECT_KEY_MISMATCH" - | "SHARE_LINK_REVOKED" - | "UNAUTHENTICATED" - | "UNSUPPORTED_OP" - | "UPLOAD_INTENT_NOT_FOUND"; +export const errorCodes = [ + "CLIENT_UPGRADE_REQUIRED", + "CONFLICT", + "ELEMENT_STRATEGY_MISMATCH", + "ELEMENT_TYPE_PAYLOAD_KIND_MISMATCH", + "FORBIDDEN", + "INTERNAL_ERROR", + "INVALID_ELEMENT_PAYLOAD_DATA", + "INVALID_ELEMENT_PAYLOAD_KIND", + "INVALID_ELEMENT_PAYLOAD_VERSION", + "INVALID_LINEUP_PAYLOAD_DATA", + "INVALID_LINEUP_PAYLOAD_KIND", + "INVALID_LINEUP_PAYLOAD_VERSION", + "INVALID_OP", + "INVALID_PAGE_CONTENT_COUNT", + "INVALID_PAYLOAD", + "INVITE_EXPIRED", + "INVITE_REVOKED", + "LINEUP_STRATEGY_MISMATCH", + "MISSING_ADD_ELEMENT_ARGS", + "MISSING_ADD_LINEUP_ARGS", + "MISSING_ELEMENT_PAYLOAD", + "MISSING_ENTITY_PUBLIC_ID", + "MISSING_LINEUP_PAYLOAD", + "MISSING_PAGE_ID", + "MISSING_PAGE_PUBLIC_ID", + "NOT_FOUND", + "PAGE_DESCRIPTOR_REQUIRES_PAGE_OP", + "PAGE_SETTINGS_REQUIRE_PAGE_CONTENT", + "PAGE_STRATEGY_MISMATCH", + "R2_OBJECT_KEY_MISMATCH", + "SHARE_LINK_REVOKED", + "UNAUTHENTICATED", + "UNSUPPORTED_OP", + "UPLOAD_INTENT_NOT_FOUND", +] as const; + +export type ErrorCode = (typeof errorCodes)[number]; type ErrorData = { code: ErrorCode; diff --git a/convex/lib/opTypes.ts b/convex/lib/opTypes.ts index e0c35df4..17e465f4 100644 --- a/convex/lib/opTypes.ts +++ b/convex/lib/opTypes.ts @@ -1,41 +1,239 @@ -import { v } from "convex/values"; +import { v, type Infer } from "convex/values"; import { elementPayloadValidator, lineupGroupPayloadValidator, + mapThemePaletteValidator, pagePayloadValidator, strategyPatchPayloadValidator, + strategySettingsValidator, } from "./payloadValidators"; -export const opKindValidator = v.union( - v.literal("add"), - v.literal("move"), - v.literal("patch"), - v.literal("delete"), - v.literal("reorder"), -); +const strategyPatchOpValidator = v.object({ + opId: v.string(), + type: v.literal("strategy.patch"), + payload: strategyPatchPayloadValidator, + expectedStrategyRevision: v.number(), +}); -export const entityTypeValidator = v.union( - v.literal("strategy"), - v.literal("page"), - v.literal("pageContent"), - v.literal("element"), - v.literal("lineup"), -); +const pageAddOpValidator = v.object({ + opId: v.string(), + type: v.literal("page.add"), + pagePublicId: v.string(), + payload: pagePayloadValidator, + sortIndex: v.number(), + expectedStrategyRevision: v.number(), +}); + +const pagePatchOpValidator = v.object({ + opId: v.string(), + type: v.literal("page.patch"), + pagePublicId: v.string(), + payload: pagePayloadValidator, + expectedPageRevision: v.number(), +}); + +const pageDeleteOpValidator = v.object({ + opId: v.string(), + type: v.literal("page.delete"), + pagePublicId: v.string(), + expectedStrategyRevision: v.number(), +}); + +const pageReorderOpValidator = v.object({ + opId: v.string(), + type: v.literal("page.reorder"), + pagePublicId: v.string(), + sortIndex: v.number(), + expectedStrategyRevision: v.number(), +}); + +const pageContentPatchOpValidator = v.object({ + opId: v.string(), + type: v.literal("pageContent.patch"), + pagePublicId: v.string(), + settings: strategySettingsValidator, + expectedPageContentRevision: v.number(), +}); + +const elementAddOpValidator = v.object({ + opId: v.string(), + type: v.literal("element.add"), + elementPublicId: v.string(), + pagePublicId: v.string(), + payload: elementPayloadValidator, + sortIndex: v.number(), + expectedElementRevision: v.optional(v.number()), +}); + +const elementPatchOpValidator = v.object({ + opId: v.string(), + type: v.literal("element.patch"), + elementPublicId: v.string(), + pagePublicId: v.optional(v.string()), + payload: v.optional(elementPayloadValidator), + sortIndex: v.optional(v.number()), + expectedElementRevision: v.number(), +}); + +const elementDeleteOpValidator = v.object({ + opId: v.string(), + type: v.literal("element.delete"), + elementPublicId: v.string(), + pagePublicId: v.string(), + expectedElementRevision: v.number(), +}); + +const elementReorderOpValidator = v.object({ + opId: v.string(), + type: v.literal("element.reorder"), + elementPublicId: v.string(), + pagePublicId: v.string(), + sortIndex: v.number(), + expectedElementRevision: v.number(), +}); + +const lineupAddOpValidator = v.object({ + opId: v.string(), + type: v.literal("lineup.add"), + lineupPublicId: v.string(), + pagePublicId: v.string(), + payload: lineupGroupPayloadValidator, + sortIndex: v.number(), + expectedLineupRevision: v.optional(v.number()), +}); -export const strategyOpValidator = v.object({ +const lineupPatchOpValidator = v.object({ opId: v.string(), - kind: opKindValidator, - entityType: entityTypeValidator, - entityPublicId: v.optional(v.string()), + type: v.literal("lineup.patch"), + lineupPublicId: v.string(), pagePublicId: v.optional(v.string()), - payload: v.optional( - v.union( - strategyPatchPayloadValidator, - pagePayloadValidator, - elementPayloadValidator, - lineupGroupPayloadValidator, - ), - ), + payload: v.optional(lineupGroupPayloadValidator), sortIndex: v.optional(v.number()), - expectedRevision: v.optional(v.number()), + expectedLineupRevision: v.number(), +}); + +const lineupDeleteOpValidator = v.object({ + opId: v.string(), + type: v.literal("lineup.delete"), + lineupPublicId: v.string(), + pagePublicId: v.string(), + expectedLineupRevision: v.number(), +}); + +const lineupReorderOpValidator = v.object({ + opId: v.string(), + type: v.literal("lineup.reorder"), + lineupPublicId: v.string(), + pagePublicId: v.string(), + sortIndex: v.number(), + expectedLineupRevision: v.number(), +}); + +export const strategyOpValidator = v.union( + strategyPatchOpValidator, + pageAddOpValidator, + pagePatchOpValidator, + pageDeleteOpValidator, + pageReorderOpValidator, + pageContentPatchOpValidator, + elementAddOpValidator, + elementPatchOpValidator, + elementDeleteOpValidator, + elementReorderOpValidator, + lineupAddOpValidator, + lineupPatchOpValidator, + lineupDeleteOpValidator, + lineupReorderOpValidator, +); + +export type StrategyOp = Infer; + +export const opRejectionReasonValidator = v.union( + v.literal("already_exists"), + v.literal("element_strategy_mismatch"), + v.literal("lineup_strategy_mismatch"), + v.literal("missing_expected_revision"), + v.literal("not_found"), + v.literal("page_strategy_mismatch"), + v.literal("revision_mismatch"), +); + +const strategyCurrentValidator = v.object({ + type: v.literal("strategy"), + revision: v.number(), + value: v.object({ + name: v.string(), + mapData: v.string(), + themeProfileId: v.union(v.string(), v.null()), + themeOverridePalette: v.union(mapThemePaletteValidator, v.null()), + }), +}); + +const pageCurrentValidator = v.object({ + type: v.literal("page"), + revision: v.number(), + value: v.object({ + name: v.string(), + isAttack: v.boolean(), + sortIndex: v.number(), + }), +}); + +const pageContentCurrentValidator = v.object({ + type: v.literal("pageContent"), + revision: v.number(), + value: v.object({ + settings: v.union(strategySettingsValidator, v.null()), + }), +}); + +const elementCurrentValidator = v.object({ + type: v.literal("element"), + revision: v.number(), + value: elementPayloadValidator, +}); + +const lineupCurrentValidator = v.object({ + type: v.literal("lineup"), + revision: v.number(), + value: lineupGroupPayloadValidator, +}); + +export const currentOpSnapshotValidator = v.union( + strategyCurrentValidator, + pageCurrentValidator, + pageContentCurrentValidator, + elementCurrentValidator, + lineupCurrentValidator, +); + +export const operationResultValidator = v.union( + v.object({ + opId: v.string(), + status: v.literal("applied"), + appliedRevision: v.number(), + }), + v.object({ + opId: v.string(), + status: v.literal("noop"), + currentRevision: v.optional(v.number()), + }), + v.object({ + opId: v.string(), + status: v.literal("rejected"), + reason: opRejectionReasonValidator, + current: v.optional(currentOpSnapshotValidator), + }), + v.object({ + opId: v.string(), + status: v.literal("failed"), + code: v.string(), + rawCode: v.string(), + message: v.string(), + }), +); + +export const applyBatchResultValidator = v.object({ + strategyPublicId: v.string(), + results: v.array(operationResultValidator), }); diff --git a/convex/lib/opTypes.typecheck.ts b/convex/lib/opTypes.typecheck.ts new file mode 100644 index 00000000..ea4e2748 --- /dev/null +++ b/convex/lib/opTypes.typecheck.ts @@ -0,0 +1,19 @@ +import type { StrategyOp } from "./opTypes"; + +const validPageDelete: StrategyOp = { + opId: "page-delete", + type: "page.delete", + pagePublicId: "page-a", + expectedStrategyRevision: 1, +}; + +const illegalEntityActionPair: StrategyOp = { + opId: "illegal-page-delete", + type: "page.delete", + // @ts-expect-error An element id cannot be paired with a page discriminator. + elementPublicId: "element-a", + expectedStrategyRevision: 1, +}; + +void validPageDelete; +void illegalEntityActionPair; diff --git a/convex/lib/publicValidators.ts b/convex/lib/publicValidators.ts new file mode 100644 index 00000000..2718d07f --- /dev/null +++ b/convex/lib/publicValidators.ts @@ -0,0 +1,186 @@ +import { v } from "convex/values"; +import { + elementPayloadKindValidator, + elementPayloadValidator, + lineupGroupPayloadValidator, + mapThemePaletteValidator, + strategySettingsValidator, +} from "./payloadValidators"; + +export const accessRoleValidator = v.union( + v.literal("owner"), + v.literal("editor"), + v.literal("viewer"), +); + +export const collaboratorRoleValidator = v.union( + v.literal("editor"), + v.literal("viewer"), +); + +export const okResultValidator = v.object({ ok: v.literal(true) }); + +export const createResultValidator = v.union( + okResultValidator, + v.object({ ok: v.literal(true), reused: v.literal(true) }), +); + +export const revisionResultValidator = v.union( + v.object({ ok: v.literal(true), revision: v.number() }), + v.object({ + ok: v.literal(true), + reused: v.literal(true), + revision: v.number(), + }), +); + +export const strategyHeaderValidator = v.object({ + publicId: v.string(), + name: v.string(), + mapData: v.string(), + revision: v.number(), + createdAt: v.number(), + updatedAt: v.number(), + themeProfileId: v.union(v.string(), v.null()), + themeOverridePalette: v.union(mapThemePaletteValidator, v.null()), + role: accessRoleValidator, +}); + +export const strategySummaryValidator = v.object({ + publicId: v.string(), + name: v.string(), + mapData: v.string(), + revision: v.number(), + createdAt: v.number(), + updatedAt: v.number(), + role: accessRoleValidator, + attackLabel: v.union( + v.literal("Unknown"), + v.literal("Mixed"), + v.literal("Attack"), + v.literal("Defend"), + ), + folderPublicId: v.union(v.string(), v.null()), + themeProfileId: v.union(v.string(), v.null()), + themeOverridePalette: v.union(mapThemePaletteValidator, v.null()), +}); + +export const folderSummaryValidator = v.object({ + publicId: v.string(), + name: v.string(), + iconId: v.union(v.number(), v.null()), + iconCodePoint: v.union(v.number(), v.null()), + iconFontFamily: v.union(v.string(), v.null()), + iconFontPackage: v.union(v.string(), v.null()), + color: v.union(v.string(), v.null()), + customColorValue: v.union(v.number(), v.null()), + parentFolderPublicId: v.union(v.string(), v.null()), + createdAt: v.number(), + updatedAt: v.number(), + role: accessRoleValidator, +}); + +export const pageDescriptorValidator = v.object({ + publicId: v.string(), + strategyPublicId: v.string(), + name: v.string(), + sortIndex: v.number(), + isAttack: v.boolean(), + revision: v.number(), + createdAt: v.number(), + updatedAt: v.number(), +}); + +export const pageContentValidator = v.object({ + settings: v.union(strategySettingsValidator, v.null()), + revision: v.number(), + createdAt: v.number(), + updatedAt: v.number(), +}); + +export const fullPageValidator = v.object({ + publicId: v.string(), + strategyPublicId: v.string(), + name: v.string(), + sortIndex: v.number(), + isAttack: v.boolean(), + revision: v.number(), + createdAt: v.number(), + updatedAt: v.number(), + settings: v.union(strategySettingsValidator, v.null()), + contentRevision: v.number(), + contentCreatedAt: v.number(), + contentUpdatedAt: v.number(), +}); + +export const elementValidator = v.object({ + publicId: v.string(), + strategyPublicId: v.string(), + pagePublicId: v.string(), + elementType: elementPayloadKindValidator, + payload: elementPayloadValidator, + sortIndex: v.number(), + revision: v.number(), + deleted: v.boolean(), + createdAt: v.number(), + updatedAt: v.number(), +}); + +export const lineupValidator = v.object({ + publicId: v.string(), + strategyPublicId: v.string(), + pagePublicId: v.string(), + payload: lineupGroupPayloadValidator, + sortIndex: v.number(), + revision: v.number(), + deleted: v.boolean(), + createdAt: v.number(), + updatedAt: v.number(), +}); + +export const imageProviderValidator = v.union( + v.literal("convex"), + v.literal("r2"), +); + +export const imageUploadStatusValidator = v.union( + v.literal("pending"), + v.literal("active"), + v.literal("failed"), + v.literal("deleted"), +); + +export const imageAssetValidator = v.object({ + publicId: v.string(), + provider: imageProviderValidator, + uploadStatus: imageUploadStatusValidator, + fileExtension: v.string(), + mimeType: v.union(v.string(), v.null()), + width: v.union(v.number(), v.null()), + height: v.union(v.number(), v.null()), + byteSize: v.union(v.number(), v.null()), + uploadedAt: v.union(v.number(), v.null()), + url: v.union(v.string(), v.null()), + legacyStoragePath: v.union(v.string(), v.null()), +}); + +export const strategyShellValidator = v.object({ + header: strategyHeaderValidator, + pages: v.array(pageDescriptorValidator), +}); + +export const pageSnapshotValidator = v.object({ + page: pageDescriptorValidator, + content: pageContentValidator, + elements: v.array(elementValidator), + lineups: v.array(lineupValidator), + assets: v.array(imageAssetValidator), +}); + +export const fullStrategySnapshotValidator = v.object({ + header: strategyHeaderValidator, + pages: v.array(fullPageValidator), + elements: v.array(elementValidator), + lineups: v.array(lineupValidator), + assets: v.array(imageAssetValidator), +}); diff --git a/convex/lineups.ts b/convex/lineups.ts index 96cfcc10..721afed7 100644 --- a/convex/lineups.ts +++ b/convex/lineups.ts @@ -3,12 +3,14 @@ import { v } from "convex/values"; import { assertStrategyRole } from "./lib/auth"; import { getPageByPublicId, getStrategyByPublicId } from "./lib/entities"; import { errorWithCode } from "./lib/errors"; +import { lineupValidator } from "./lib/publicValidators"; export const listForPage = query({ args: { strategyPublicId: v.string(), pagePublicId: v.string(), }, + returns: v.array(lineupValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); @@ -43,6 +45,7 @@ export const listForStrategy = query({ args: { strategyPublicId: v.string(), }, + returns: v.array(lineupValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); diff --git a/convex/ops.ts b/convex/ops.ts index 7f3a56ac..166f8970 100644 --- a/convex/ops.ts +++ b/convex/ops.ts @@ -1,5 +1,5 @@ import { mutation, type MutationCtx } from "./_generated/server"; -import { ConvexError, v } from "convex/values"; +import { ConvexError, v, type Infer } from "convex/values"; import type { Doc, Id } from "./_generated/dataModel"; import { assertStrategyRole } from "./lib/auth"; import { @@ -7,7 +7,13 @@ import { getStrategyByPublicId, sortByNumberField, } from "./lib/entities"; -import { strategyOpValidator } from "./lib/opTypes"; +import { + applyBatchResultValidator, + currentOpSnapshotValidator, + operationResultValidator, + strategyOpValidator, + type StrategyOp as WireStrategyOp, +} from "./lib/opTypes"; import { assertSupportedCloudProtocol } from "./lib/cloudProtocol"; import { valuesEqual } from "./lib/canonicalValues"; import { errorWithCode, invalidPayloadError } from "./lib/errors"; @@ -30,7 +36,8 @@ type PagePayload = { }; type StrategyOp = { opId: string; - kind: "add" | "move" | "patch" | "delete" | "reorder"; + type: WireStrategyOp["type"]; + kind: "add" | "patch" | "delete" | "reorder"; entityType: "strategy" | "page" | "pageContent" | "element" | "lineup"; entityPublicId?: string; pagePublicId?: string; @@ -43,14 +50,179 @@ type TargetSnapshot = { payload: unknown; }; type OperationResult = { - status: "ack" | "reject"; + status: "ack" | "reject" | "failed"; reason?: string; appliedRevision?: number; latestRevision?: number; latestPayload?: unknown; + code?: string; + rawCode?: string; + message?: string; eventPageId?: Id<"pages">; }; +type CurrentTarget = StrategyOp["entityType"]; +type PublicOperationResult = Infer; + +function normalizeOp(op: WireStrategyOp): StrategyOp { + switch (op.type) { + case "strategy.patch": + return { + opId: op.opId, + type: op.type, + kind: "patch", + entityType: "strategy", + payload: op.payload, + expectedRevision: op.expectedStrategyRevision, + }; + case "page.add": + return { + opId: op.opId, + type: op.type, + kind: "add", + entityType: "page", + entityPublicId: op.pagePublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + sortIndex: op.sortIndex, + expectedRevision: op.expectedStrategyRevision, + }; + case "page.patch": + return { + opId: op.opId, + type: op.type, + kind: "patch", + entityType: "page", + entityPublicId: op.pagePublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + expectedRevision: op.expectedPageRevision, + }; + case "page.delete": + return { + opId: op.opId, + type: op.type, + kind: "delete", + entityType: "page", + entityPublicId: op.pagePublicId, + pagePublicId: op.pagePublicId, + expectedRevision: op.expectedStrategyRevision, + }; + case "page.reorder": + return { + opId: op.opId, + type: op.type, + kind: "reorder", + entityType: "page", + entityPublicId: op.pagePublicId, + pagePublicId: op.pagePublicId, + sortIndex: op.sortIndex, + expectedRevision: op.expectedStrategyRevision, + }; + case "pageContent.patch": + return { + opId: op.opId, + type: op.type, + kind: "patch", + entityType: "pageContent", + entityPublicId: op.pagePublicId, + pagePublicId: op.pagePublicId, + payload: { settings: op.settings }, + expectedRevision: op.expectedPageContentRevision, + }; + case "element.add": + return { + opId: op.opId, + type: op.type, + kind: "add", + entityType: "element", + entityPublicId: op.elementPublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + sortIndex: op.sortIndex, + expectedRevision: op.expectedElementRevision, + }; + case "element.patch": + return { + opId: op.opId, + type: op.type, + kind: "patch", + entityType: "element", + entityPublicId: op.elementPublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + sortIndex: op.sortIndex, + expectedRevision: op.expectedElementRevision, + }; + case "element.delete": + return { + opId: op.opId, + type: op.type, + kind: "delete", + entityType: "element", + entityPublicId: op.elementPublicId, + pagePublicId: op.pagePublicId, + expectedRevision: op.expectedElementRevision, + }; + case "element.reorder": + return { + opId: op.opId, + type: op.type, + kind: "reorder", + entityType: "element", + entityPublicId: op.elementPublicId, + pagePublicId: op.pagePublicId, + sortIndex: op.sortIndex, + expectedRevision: op.expectedElementRevision, + }; + case "lineup.add": + return { + opId: op.opId, + type: op.type, + kind: "add", + entityType: "lineup", + entityPublicId: op.lineupPublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + sortIndex: op.sortIndex, + expectedRevision: op.expectedLineupRevision, + }; + case "lineup.patch": + return { + opId: op.opId, + type: op.type, + kind: "patch", + entityType: "lineup", + entityPublicId: op.lineupPublicId, + pagePublicId: op.pagePublicId, + payload: op.payload, + sortIndex: op.sortIndex, + expectedRevision: op.expectedLineupRevision, + }; + case "lineup.delete": + return { + opId: op.opId, + type: op.type, + kind: "delete", + entityType: "lineup", + entityPublicId: op.lineupPublicId, + pagePublicId: op.pagePublicId, + expectedRevision: op.expectedLineupRevision, + }; + case "lineup.reorder": + return { + opId: op.opId, + type: op.type, + kind: "reorder", + entityType: "lineup", + entityPublicId: op.lineupPublicId, + pagePublicId: op.pagePublicId, + sortIndex: op.sortIndex, + expectedRevision: op.expectedLineupRevision, + }; + } +} + function isRecord(payload: unknown): payload is Record { return ( typeof payload === "object" && payload !== null && !Array.isArray(payload) @@ -304,6 +476,82 @@ function noop(revision?: number, eventPageId?: Id<"pages">): OperationResult { }; } +function currentTargetForOp(op: StrategyOp): CurrentTarget { + if ( + op.entityType === "page" && + (op.kind === "add" || op.kind === "delete" || op.kind === "reorder") + ) { + return "strategy"; + } + return op.entityType; +} + +function isRejectionReason( + reason: string, +): reason is Extract["reason"] { + return ( + reason === "already_exists" || + reason === "element_strategy_mismatch" || + reason === "lineup_strategy_mismatch" || + reason === "missing_expected_revision" || + reason === "not_found" || + reason === "page_strategy_mismatch" || + reason === "revision_mismatch" + ); +} + +function toPublicResult( + op: StrategyOp, + result: OperationResult, +): PublicOperationResult { + if (result.status === "failed") { + return { + opId: op.opId, + status: "failed", + code: result.code ?? "INTERNAL_ERROR", + rawCode: result.rawCode ?? "INTERNAL_ERROR", + message: result.message ?? "Unexpected Convex function failure", + }; + } + if (result.status === "ack" && result.reason === "noop") { + return { + opId: op.opId, + status: "noop", + ...(result.appliedRevision === undefined + ? {} + : { currentRevision: result.appliedRevision }), + }; + } + if (result.status === "ack") { + if (result.appliedRevision === undefined) { + throw new Error(`Applied op ${op.opId} did not return a revision`); + } + return { + opId: op.opId, + status: "applied", + appliedRevision: result.appliedRevision, + }; + } + const reason = result.reason ?? "not_found"; + if (!isRejectionReason(reason)) { + throw new Error(`Unknown op rejection reason: ${reason}`); + } + return { + opId: op.opId, + status: "rejected", + reason, + ...(result.latestRevision === undefined || result.latestPayload === undefined + ? {} + : { + current: { + type: currentTargetForOp(op), + revision: result.latestRevision, + value: result.latestPayload, + } as Infer, + }), + }; +} + async function applyStrategyOp( ctx: MutationCtx, strategy: Doc<"strategies">, @@ -414,7 +662,7 @@ async function applyPageOp( strategy, result: rejected( "already_exists", - { revision: strategy.revision, payload: pagePayload(existing) }, + { revision: strategy.revision, payload: strategyPayload(strategy) }, existing._id, ), }; @@ -799,7 +1047,7 @@ async function applyElementOp( } const patch: Record = {}; let eventPageId = existing.pageId; - if (op.kind === "patch" || op.kind === "move") { + if (op.kind === "patch") { if (op.payload !== undefined) { const payload = assertElementPayload(op.payload); if (payload.kind !== existing.elementType) { @@ -967,7 +1215,7 @@ async function applyLineupOp( } const patch: Record = {}; let eventPageId = existing.pageId; - if (op.kind === "patch" || op.kind === "move") { + if (op.kind === "patch") { if (op.payload !== undefined) { const payload = assertLineupPayload(op.payload); setIfChanged(patch, "payload", existing.payload, payload); @@ -1027,17 +1275,18 @@ export const applyBatch = mutation({ clientProtocolVersion: v.number(), ops: v.array(strategyOpValidator), }, + returns: applyBatchResultValidator, handler: async (ctx, args) => { assertSupportedCloudProtocol(args.clientProtocolVersion); let strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); - const results: Array> = []; + const results: PublicOperationResult[] = []; // Outcomes are per operation: accepted changes and visible rejections are // committed together by this single Convex transaction. One stale op must // not erase an independent op that the server already accepted. for (const rawOp of args.ops) { - const op = rawOp as StrategyOp; + const op = normalizeOp(rawOp); const existingEvent = await ctx.db .query("operationEvents") .withIndex("by_strategyId_clientId_opId", (q) => @@ -1049,15 +1298,23 @@ export const applyBatch = mutation({ .first(); if (existingEvent !== null) { const latest = await getTargetSnapshot(ctx, strategy, op); - results.push({ - opId: op.opId, - status: existingEvent.status, - reason: existingEvent.reason ?? null, - appliedRevision: existingEvent.appliedRevision ?? null, - expectedRevision: existingEvent.expectedRevision ?? null, - latestRevision: latest?.revision ?? null, - latestPayload: latest?.payload ?? null, - }); + const replayResult: OperationResult = + existingEvent.status === "failed" + ? { + status: "failed", + code: existingEvent.code, + rawCode: existingEvent.rawCode, + message: existingEvent.message, + } + : existingEvent.status === "rejected" + ? { + status: "reject", + reason: existingEvent.reason, + latestRevision: latest?.revision, + latestPayload: latest?.payload, + } + : noop(latest?.revision); + results.push(toPublicResult(op, replayResult)); continue; } @@ -1080,35 +1337,48 @@ export const applyBatch = mutation({ } } catch (error) { if (!(error instanceof ConvexError)) throw error; - const code = + const rawCode = typeof error.data?.code === "string" - ? error.data.code.toLowerCase() - : "internal_error"; - const latest = await getTargetSnapshot(ctx, strategy, op); - result = rejected(code, latest); + ? error.data.code + : "INTERNAL_ERROR"; + const message = + typeof error.data?.message === "string" + ? error.data.message + : error.message; + result = { + status: "failed", + code: rawCode, + rawCode, + message, + }; } + const publicResult = toPublicResult(op, result); + await ctx.db.insert("operationEvents", { strategyId: strategy._id, pageId: result.eventPageId, clientId: args.clientId, opId: op.opId, - opType: `${op.entityType}.${op.kind}`, - status: result.status, - reason: result.reason, + opType: op.type, + status: publicResult.status, + reason: + publicResult.status === "rejected" ? publicResult.reason : undefined, + code: publicResult.status === "failed" ? publicResult.code : undefined, + rawCode: + publicResult.status === "failed" ? publicResult.rawCode : undefined, + message: + publicResult.status === "failed" ? publicResult.message : undefined, expectedRevision: op.expectedRevision, - appliedRevision: result.appliedRevision, + appliedRevision: + publicResult.status === "applied" + ? publicResult.appliedRevision + : publicResult.status === "noop" + ? publicResult.currentRevision + : undefined, createdAt: Date.now(), }); - results.push({ - opId: op.opId, - status: result.status, - reason: result.reason ?? null, - appliedRevision: result.appliedRevision ?? null, - expectedRevision: op.expectedRevision ?? null, - latestRevision: result.latestRevision ?? null, - latestPayload: result.latestPayload ?? null, - }); + results.push(publicResult); } return { strategyPublicId: strategy.publicId, results }; diff --git a/convex/page.ts b/convex/page.ts index c292f0d0..1b130eb5 100644 --- a/convex/page.ts +++ b/convex/page.ts @@ -15,12 +15,14 @@ import { serializePageContent, serializePageDescriptor, } from "./lib/snapshotSerialization"; +import { pageSnapshotValidator } from "./lib/publicValidators"; export const getSnapshot = query({ args: { strategyPublicId: v.string(), pagePublicId: v.string(), }, + returns: pageSnapshotValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); diff --git a/convex/pages.ts b/convex/pages.ts index 4c77c474..1a82b414 100644 --- a/convex/pages.ts +++ b/convex/pages.ts @@ -18,9 +18,14 @@ import { } from "./lib/errors"; import { serializePageDescriptor } from "./lib/snapshotSerialization"; import { valuesEqual } from "./lib/canonicalValues"; +import { + pageDescriptorValidator, + revisionResultValidator, +} from "./lib/publicValidators"; export const listForStrategy = query({ args: { strategyPublicId: v.string() }, + returns: v.array(pageDescriptorValidator), handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "viewer"); @@ -44,6 +49,7 @@ export const add = mutation({ isAttack: v.boolean(), settings: v.optional(strategySettingsValidator), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -81,7 +87,7 @@ export const add = mutation({ existingPage.isAttack === args.isAttack && valuesEqual(pageContents[0]!.settings, args.settings); if (identical) { - return { ok: true, reused: true, revision: strategy.revision }; + return { ok: true, reused: true, revision: strategy.revision } as const; } throw conflictError(`Page publicId already exists: ${args.pagePublicId}`); } @@ -122,7 +128,7 @@ export const add = mutation({ }); const revision = strategy.revision + 1; await ctx.db.patch(strategy._id, { revision, updatedAt: now }); - return { ok: true, revision }; + return { ok: true, revision } as const; }, }); @@ -133,6 +139,7 @@ export const rename = mutation({ name: v.string(), expectedRevision: v.number(), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -141,7 +148,7 @@ export const rename = mutation({ throw errorWithCode("PAGE_STRATEGY_MISMATCH", "Page strategy mismatch"); } if (page.name === args.name) { - return { ok: true, reused: true, revision: page.revision }; + return { ok: true, reused: true, revision: page.revision } as const; } if (args.expectedRevision !== page.revision) { throw conflictError("Page revision mismatch"); @@ -153,16 +160,17 @@ export const rename = mutation({ revision, updatedAt: Date.now(), }); - return { ok: true, revision }; + return { ok: true, revision } as const; }, }); -export const deletePage = mutation({ +const deletePage = mutation({ args: { strategyPublicId: v.string(), pagePublicId: v.string(), expectedRevision: v.number(), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -174,7 +182,7 @@ export const deletePage = mutation({ (candidate) => candidate.publicId === args.pagePublicId, ); if (page === undefined) { - return { ok: true, reused: true, revision: strategy.revision }; + return { ok: true, reused: true, revision: strategy.revision } as const; } if (pages.length <= 1) { throw invalidOpError("Cannot delete last page"); @@ -213,7 +221,7 @@ export const deletePage = mutation({ const revision = strategy.revision + 1; await ctx.db.patch(strategy._id, { revision, updatedAt: now }); - return { ok: true, revision }; + return { ok: true, revision } as const; }, }); @@ -223,6 +231,7 @@ export const reorder = mutation({ orderedPagePublicIds: v.array(v.string()), expectedRevision: v.number(), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -243,7 +252,7 @@ export const reorder = mutation({ return page; }); if (ordered.every((page, index) => page.sortIndex === index)) { - return { ok: true, reused: true, revision: strategy.revision }; + return { ok: true, reused: true, revision: strategy.revision } as const; } if (args.expectedRevision !== strategy.revision) { throw conflictError("Strategy revision mismatch"); @@ -262,7 +271,7 @@ export const reorder = mutation({ } const revision = strategy.revision + 1; await ctx.db.patch(strategy._id, { revision, updatedAt: now }); - return { ok: true, revision }; + return { ok: true, revision } as const; }, }); diff --git a/convex/schema.ts b/convex/schema.ts index 2a9fe2e1..76c4b7bf 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -154,6 +154,7 @@ export default defineSchema({ .index("by_strategyId", ["strategyId"]), imageAssets: defineTable({ publicId: v.string(), + uploadAttemptPublicId: v.optional(v.string()), provider: v.optional(v.union(v.literal("convex"), v.literal("r2"))), strategyId: v.optional(v.id("strategies")), createdByUserId: v.optional(v.id("users")), @@ -181,6 +182,7 @@ export default defineSchema({ storagePath: v.optional(v.string()), }) .index("by_publicId", ["publicId"]) + .index("by_uploadAttemptPublicId", ["uploadAttemptPublicId"]) .index("by_strategyId", ["strategyId"]) .index("by_strategyId_and_uploadStatus_and_updatedAt", [ "strategyId", @@ -201,8 +203,16 @@ export default defineSchema({ clientId: v.string(), opId: v.string(), opType: v.string(), - status: v.union(v.literal("ack"), v.literal("reject")), + status: v.union( + v.literal("applied"), + v.literal("noop"), + v.literal("rejected"), + v.literal("failed"), + ), reason: v.optional(v.string()), + code: v.optional(v.string()), + rawCode: v.optional(v.string()), + message: v.optional(v.string()), expectedRevision: v.optional(v.number()), appliedRevision: v.optional(v.number()), createdAt: v.number(), diff --git a/convex/shares.ts b/convex/shares.ts index bd8cf943..77ecde60 100644 --- a/convex/shares.ts +++ b/convex/shares.ts @@ -14,6 +14,10 @@ import { errorWithCode, conflictError, } from "./lib/errors"; +import { + accessRoleValidator, + okResultValidator, +} from "./lib/publicValidators"; const targetTypeValidator = v.union(v.literal("strategy"), v.literal("folder")); const collaboratorRoleValidator = v.union(v.literal("viewer"), v.literal("editor")); @@ -38,6 +42,14 @@ export const list = query({ targetType: targetTypeValidator, targetPublicId: v.string(), }, + returns: v.array( + v.object({ + token: v.string(), + role: collaboratorRoleValidator, + createdAt: v.number(), + revokedAt: v.union(v.number(), v.null()), + }), + ), handler: async (ctx, args) => { const resolved = await resolveTarget(ctx, args.targetType, args.targetPublicId); @@ -76,6 +88,7 @@ export const create = mutation({ token: v.string(), role: collaboratorRoleValidator, }, + returns: okResultValidator, handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const resolved = await resolveTarget(ctx, args.targetType, args.targetPublicId); @@ -105,7 +118,7 @@ export const create = mutation({ updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -115,6 +128,7 @@ export const revoke = mutation({ targetPublicId: v.string(), token: v.string(), }, + returns: okResultValidator, handler: async (ctx, args) => { const resolved = await resolveTarget(ctx, args.targetType, args.targetPublicId); @@ -145,7 +159,7 @@ export const revoke = mutation({ updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true } as const; }, }); @@ -153,6 +167,21 @@ export const redeem = mutation({ args: { token: v.string(), }, + returns: v.union( + v.object({ + ok: v.literal(true), + targetType: v.literal("strategy"), + strategyPublicId: v.string(), + folderPublicId: v.union(v.string(), v.null()), + role: accessRoleValidator, + }), + v.object({ + ok: v.literal(true), + targetType: v.literal("folder"), + folderPublicId: v.string(), + role: accessRoleValidator, + }), + ), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const link = await ctx.db @@ -218,7 +247,7 @@ export const redeem = mutation({ strategyPublicId: strategy.publicId, folderPublicId: folder?.publicId ?? null, role: strategy.ownerId === user._id ? "owner" : redeemedRole, - }; + } as const; } const folder = link.folderId === undefined ? null : await ctx.db.get(link.folderId); @@ -263,6 +292,6 @@ export const redeem = mutation({ targetType: "folder", folderPublicId: folder.publicId, role: folder.ownerId === user._id ? "owner" : redeemedRole, - }; + } as const; }, }); diff --git a/convex/strategies.ts b/convex/strategies.ts index 62872513..e8850a3f 100644 --- a/convex/strategies.ts +++ b/convex/strategies.ts @@ -21,6 +21,13 @@ import { forbiddenError, } from "./lib/errors"; import { purgeDeletedPageOrphansRef } from "./maintenance"; +import { + createResultValidator, + okResultValidator, + revisionResultValidator, + strategyHeaderValidator, + strategySummaryValidator, +} from "./lib/publicValidators"; type StrategyScope = "owned" | "shared" | "all"; @@ -126,10 +133,12 @@ async function summarizeStrategies( createdAt: number; updatedAt: number; role: StrategyRole; - attackLabel: string; + attackLabel: "Unknown" | "Mixed" | "Attack" | "Defend"; folderPublicId: string | null; themeProfileId: string | null; - themeOverridePalette: Doc<"strategies">["themeOverridePalette"] | null; + themeOverridePalette: + | NonNullable["themeOverridePalette"]> + | null; }> => { const pagesPromise = ctx.db .query("pages") @@ -148,7 +157,7 @@ async function summarizeStrategies( folderPromise, folderRolePromise, ]); - let attackLabel = "Unknown"; + let attackLabel: "Unknown" | "Mixed" | "Attack" | "Defend" = "Unknown"; if (pages.length > 0) { const first = pages[0]!.isAttack; const mixed = pages.some((page) => page.isAttack !== first); @@ -337,7 +346,7 @@ async function createStrategyWithInitialPageRecord( now, }); } - return { ok: true, reused: true }; + return { ok: true, reused: true } as const; } if (existing.length > 0) { throw conflictError(`Strategy publicId already exists: ${args.publicId}`); @@ -364,7 +373,7 @@ async function createStrategyWithInitialPageRecord( now, }); - return { ok: true }; + return { ok: true } as const; } export const listForFolder = query({ @@ -372,6 +381,7 @@ export const listForFolder = query({ folderPublicId: v.optional(v.string()), scope: strategyScopeValidator, }, + returns: v.array(strategySummaryValidator), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); const scope = args.scope ?? "owned"; @@ -395,6 +405,7 @@ export const listForFolder = query({ export const listSharedWithMe = query({ args: {}, + returns: v.array(strategySummaryValidator), handler: async (ctx) => { const user = await requireCurrentUser(ctx); const memberships = await ctx.db @@ -418,6 +429,7 @@ export const getHeader = query({ args: { strategyPublicId: v.string(), }, + returns: strategyHeaderValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); const { role } = await assertStrategyRole(ctx, strategy, "viewer"); @@ -445,6 +457,7 @@ export const create = mutation({ themeProfileId: v.optional(v.string()), themeOverridePalette: v.optional(mapThemePaletteValidator), }, + returns: createResultValidator, handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); return await createStrategyWithInitialPageRecord(ctx, args, user._id, { @@ -468,6 +481,7 @@ export const createWithInitialPage = mutation({ themeProfileId: v.optional(v.string()), themeOverridePalette: v.optional(mapThemePaletteValidator), }, + returns: createResultValidator, handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); return await createStrategyWithInitialPageRecord(ctx, args, user._id, { @@ -490,6 +504,7 @@ export const update = mutation({ themeOverridePalette: v.optional(mapThemePaletteValidator), clearThemeOverridePalette: v.optional(v.boolean()), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -523,7 +538,7 @@ export const update = mutation({ } if (Object.keys(patch).length === 0) { - return { ok: true, reused: true, revision: strategy.revision }; + return { ok: true, reused: true, revision: strategy.revision } as const; } if (args.expectedRevision !== strategy.revision) { throw conflictError("Strategy revision mismatch"); @@ -535,7 +550,7 @@ export const update = mutation({ revision, updatedAt: Date.now(), }); - return { ok: true, revision }; + return { ok: true, revision } as const; }, }); @@ -545,6 +560,7 @@ export const move = mutation({ expectedRevision: v.number(), folderPublicId: v.optional(v.string()), }, + returns: revisionResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -562,21 +578,23 @@ export const move = mutation({ folderId = folder._id; } + const revision = strategy.revision + 1; await ctx.db.patch(strategy._id, { folderId, - revision: strategy.revision + 1, + revision, updatedAt: Date.now(), }); - return { ok: true }; + return { ok: true, revision } as const; }, }); -export const deleteStrategy = mutation({ +const deleteStrategy = mutation({ args: { strategyPublicId: v.string(), expectedRevision: v.number(), }, + returns: okResultValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "owner"); @@ -620,7 +638,7 @@ export const deleteStrategy = mutation({ } await ctx.db.delete(strategy._id); - return { ok: true }; + return { ok: true } as const; }, }); diff --git a/convex/strategy.ts b/convex/strategy.ts index 774e175c..9d905566 100644 --- a/convex/strategy.ts +++ b/convex/strategy.ts @@ -16,6 +16,10 @@ import { serializeStrategyHeader, } from "./lib/snapshotSerialization"; import { internalError } from "./lib/errors"; +import { + fullStrategySnapshotValidator, + strategyShellValidator, +} from "./lib/publicValidators"; async function getPageContent( ctx: QueryCtx, @@ -35,6 +39,7 @@ export const getShell = query({ args: { strategyPublicId: v.string(), }, + returns: strategyShellValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); const { role } = await assertStrategyRole(ctx, strategy, "viewer"); @@ -56,6 +61,7 @@ export const getFullSnapshot = query({ args: { strategyPublicId: v.string(), }, + returns: fullStrategySnapshotValidator, handler: async (ctx, args) => { const strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); const { role } = await assertStrategyRole(ctx, strategy, "viewer"); diff --git a/convex/syncBoundaries.test.ts b/convex/syncBoundaries.test.ts index 1006a70e..01e3e2d0 100644 --- a/convex/syncBoundaries.test.ts +++ b/convex/syncBoundaries.test.ts @@ -105,14 +105,115 @@ async function applyOps( return (await owner.mutation(applyBatch, { strategyPublicId, clientId, - clientProtocolVersion: 2, - ops, + clientProtocolVersion: 3, + ops: ops.map(toProtocol3Op), })) as { strategyPublicId: string; results: Array>; }; } +function toProtocol3Op(op: Record): Record { + if (typeof op.type === "string") return op; + const opId = op.opId; + const kind = op.kind; + const entityType = op.entityType; + const expectedRevision = op.expectedRevision; + if (typeof opId !== "string" || typeof kind !== "string") { + throw new Error("Invalid test op"); + } + if (entityType === "strategy" && kind === "patch") { + return { + opId, + type: "strategy.patch", + payload: op.payload ?? {}, + expectedStrategyRevision: expectedRevision, + }; + } + if (entityType === "page") { + const pagePublicId = op.entityPublicId ?? op.pagePublicId; + if (kind === "add") { + return { + opId, + type: "page.add", + pagePublicId, + payload: op.payload ?? {}, + sortIndex: op.sortIndex ?? 0, + expectedStrategyRevision: expectedRevision, + }; + } + if (kind === "patch") { + return { + opId, + type: "page.patch", + pagePublicId, + payload: op.payload ?? {}, + expectedPageRevision: expectedRevision, + }; + } + if (kind === "delete") { + return { + opId, + type: "page.delete", + pagePublicId, + expectedStrategyRevision: expectedRevision, + }; + } + if (kind === "reorder") { + return { + opId, + type: "page.reorder", + pagePublicId, + sortIndex: op.sortIndex, + expectedStrategyRevision: expectedRevision, + }; + } + } + if (entityType === "pageContent" && kind === "patch") { + const payload = op.payload as { settings?: unknown } | undefined; + return { + opId, + type: "pageContent.patch", + pagePublicId: op.entityPublicId ?? op.pagePublicId, + settings: payload?.settings, + expectedPageContentRevision: expectedRevision, + }; + } + if (entityType === "element") { + return toProtocol3ContentOp(op, opId, kind, "element"); + } + if (entityType === "lineup") { + return toProtocol3ContentOp(op, opId, kind, "lineup"); + } + throw new Error(`Illegal test op pair: ${entityType}.${kind}`); +} + +function toProtocol3ContentOp( + op: Record, + opId: string, + kind: string, + entity: "element" | "lineup", +): Record { + const capitalized = entity === "element" ? "Element" : "Lineup"; + const idKey = `${entity}PublicId`; + const expectedKey = `expected${capitalized}Revision`; + return { + opId, + type: `${entity}.${kind}`, + [idKey]: op.entityPublicId, + ...({ pagePublicId: op.pagePublicId ?? pageA }), + ...(op.payload === undefined ? {} : { payload: op.payload }), + ...((kind === "add" || kind === "reorder") && op.sortIndex === undefined + ? { sortIndex: 0 } + : op.sortIndex === undefined + ? {} + : { sortIndex: op.sortIndex }), + ...(op.expectedRevision === undefined + ? {} + : { [expectedKey]: op.expectedRevision }), + }; +} + async function getStrategyRow(t: Harness): Promise> { return await t.run(async (ctx) => { const row = await ctx.db @@ -147,7 +248,7 @@ async function addPageB(owner: Harness, expectedRevision: number) { expectedRevision, }, ]); - expect(response.results[0]).toMatchObject({ status: "ack" }); + expect(response.results[0]).toMatchObject({ status: "applied" }); } async function seedTwoPageContent(t: Harness, owner: Harness) { @@ -360,7 +461,7 @@ describe("record-scoped write contract", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 2, }); expect(await getStrategyRow(t)).toEqual(before); @@ -386,7 +487,7 @@ describe("record-scoped write contract", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: beforePage.content.revision + 1, }); const afterPage = (await owner.query(getPageSnapshot, { @@ -446,8 +547,8 @@ describe("record-scoped write contract", () => { }, ]); expect(response.results.map((result) => result.status)).toEqual([ - "ack", - "ack", + "applied", + "applied", ]); }); @@ -484,11 +585,11 @@ describe("record-scoped write contract", () => { }, ]); expect(response.results).toMatchObject([ - { status: "ack", appliedRevision: 2 }, + { status: "applied", appliedRevision: 2 }, { - status: "reject", + status: "rejected", reason: "revision_mismatch", - latestRevision: 2, + current: { type: "element", revision: 2 }, }, ]); const snapshot = (await owner.query(getPageSnapshot, { @@ -528,7 +629,7 @@ describe("record-scoped write contract", () => { }, ]); expect(accepted.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 1, }); @@ -544,9 +645,9 @@ describe("record-scoped write contract", () => { }, ]); expect(rejected.results[0]).toMatchObject({ - status: "reject", + status: "rejected", reason: "revision_mismatch", - latestRevision: 1, + current: { type: "strategy", revision: 1 }, }); }); @@ -566,7 +667,7 @@ describe("record-scoped write contract", () => { }, ]); expect(accepted.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 2, }); const shell = (await owner.query(getShell, { @@ -590,9 +691,9 @@ describe("record-scoped write contract", () => { }, ]); expect(rejected.results[0]).toMatchObject({ - status: "reject", + status: "rejected", reason: "revision_mismatch", - latestRevision: 2, + current: { type: "strategy", revision: 2 }, }); }); @@ -613,7 +714,7 @@ describe("record-scoped write contract", () => { }, ]); expect(renamed.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 2, }); @@ -629,7 +730,7 @@ describe("record-scoped write contract", () => { }, ]); expect(added.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 1, }); @@ -644,7 +745,7 @@ describe("record-scoped write contract", () => { }, ]); expect(reordered.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 2, }); @@ -658,7 +759,7 @@ describe("record-scoped write contract", () => { }, ]); expect(deleted.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 3, }); @@ -694,7 +795,7 @@ describe("record-scoped write contract", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", + status: "applied", appliedRevision: 1, }); @@ -771,14 +872,14 @@ describe("record-scoped write contract", () => { ]); expect(missingRevision.results).toMatchObject([ { - status: "reject", + status: "rejected", reason: "missing_expected_revision", - latestRevision: 2, + current: { type: "element", revision: 2 }, }, { - status: "reject", + status: "rejected", reason: "missing_expected_revision", - latestRevision: 2, + current: { type: "lineup", revision: 2 }, }, ]); @@ -803,8 +904,16 @@ describe("record-scoped write contract", () => { }, ]); expect(staleRevision.results).toMatchObject([ - { status: "reject", reason: "revision_mismatch", latestRevision: 2 }, - { status: "reject", reason: "revision_mismatch", latestRevision: 2 }, + { + status: "rejected", + reason: "revision_mismatch", + current: { type: "element", revision: 2 }, + }, + { + status: "rejected", + reason: "revision_mismatch", + current: { type: "lineup", revision: 2 }, + }, ]); const misclassifiedPatch = await applyOps(owner, "undo-restore-patch", [ @@ -830,8 +939,8 @@ describe("record-scoped write contract", () => { }, ]); expect(misclassifiedPatch.results).toMatchObject([ - { status: "ack", reason: "noop", appliedRevision: 2 }, - { status: "ack", reason: "noop", appliedRevision: 2 }, + { status: "noop", currentRevision: 2 }, + { status: "noop", currentRevision: 2 }, ]); const stillDeleted = (await owner.query(getPageSnapshot, { strategyPublicId, @@ -868,8 +977,8 @@ describe("record-scoped write contract", () => { }, ]); expect(restored.results).toMatchObject([ - { status: "ack", appliedRevision: 3 }, - { status: "ack", appliedRevision: 3 }, + { status: "applied", appliedRevision: 3 }, + { status: "applied", appliedRevision: 3 }, ]); const snapshot = (await owner.query(getPageSnapshot, { @@ -1070,15 +1179,14 @@ describe("replay safety after operation event expiry", () => { const identical = await applyOps(owner, "replay-add", [original]); expect(identical.results[0]).toMatchObject({ - status: "ack", - reason: "noop", + status: "noop", }); const different = await applyOps(owner, "replay-add-different", [ { ...original, opId: "different-add", payload: textPayload("different") }, ]); expect(different.results[0]).toMatchObject({ - status: "reject", + status: "rejected", reason: "already_exists", }); }); @@ -1117,9 +1225,8 @@ describe("replay safety after operation event expiry", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", - reason: "noop", - appliedRevision: 2, + status: "noop", + currentRevision: 2, }); }); @@ -1155,10 +1262,13 @@ describe("replay safety after operation event expiry", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "reject", + status: "rejected", reason: "revision_mismatch", - latestRevision: 2, - latestPayload: textPayload("newer"), + current: { + type: "element", + revision: 2, + value: textPayload("newer"), + }, }); }); @@ -1175,8 +1285,7 @@ describe("replay safety after operation event expiry", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", - reason: "noop", + status: "noop", }); }); @@ -1215,9 +1324,75 @@ describe("replay safety after operation event expiry", () => { }, ]); expect(response.results[0]).toMatchObject({ - status: "ack", - reason: "noop", - appliedRevision: 2, + status: "noop", + currentRevision: 2, + }); + }); +}); + +describe("cloud protocol v3 boundary", () => { + test("old clients receive a structured upgrade error", async () => { + const { owner } = await createHarness(); + + const error = await owner + .mutation(applyBatch, { + strategyPublicId, + clientId: "old-client", + clientProtocolVersion: 2, + ops: [], + }) + .then( + () => null, + (caught: unknown) => caught as { data?: unknown }, + ); + expect(error).not.toBeNull(); + expect(typeof error?.data).toBe("string"); + expect(JSON.parse(error?.data as string)).toEqual({ + code: "CLIENT_UPGRADE_REQUIRED", + message: "Client upgrade required", + }); + }); + + test("the wire validator rejects an illegal operation discriminator", async () => { + const { owner } = await createHarness(); + + await expect( + owner.mutation(applyBatch, { + strategyPublicId, + clientId: "illegal-op", + clientProtocolVersion: 3, + ops: [ + { + opId: "illegal-page-delete", + type: "page.delete", + elementPublicId: "element-a", + expectedStrategyRevision: 0, + }, + ], + }), + ).rejects.toThrow(); + }); + + test("function errors become closed failed outcomes", async () => { + const { owner } = await createHarness(); + await createBaseStrategy(owner); + + const response = await applyOps(owner, "failed-outcome", [ + { + opId: "page-settings-on-descriptor", + type: "page.patch", + pagePublicId: pageA, + payload: { settings: settingsB }, + expectedPageRevision: 1, + }, + ]); + + expect(response.results[0]).toEqual({ + opId: "page-settings-on-descriptor", + status: "failed", + code: "PAGE_SETTINGS_REQUIRE_PAGE_CONTENT", + rawCode: "PAGE_SETTINGS_REQUIRE_PAGE_CONTENT", + message: "Page settings require a pageContent operation", }); }); }); diff --git a/convex/users.ts b/convex/users.ts index 39a07409..0daeda28 100644 --- a/convex/users.ts +++ b/convex/users.ts @@ -4,9 +4,12 @@ import { getCanonicalExternalId, } from "./lib/auth"; import { unauthenticatedError } from "./lib/errors"; +import { okResultValidator } from "./lib/publicValidators"; +import { v } from "convex/values"; export const ensureCurrentUser = mutation({ args: {}, + returns: okResultValidator, handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { @@ -26,21 +29,33 @@ export const ensureCurrentUser = mutation({ avatarUrl, updatedAt: Date.now(), }); - return existingUser._id; + return { ok: true as const }; } - return await ctx.db.insert("users", { + await ctx.db.insert("users", { externalId, displayName, avatarUrl, createdAt: Date.now(), updatedAt: Date.now(), }); + return { ok: true as const }; }, }); export const me = query({ args: {}, + returns: v.union( + v.object({ + id: v.string(), + externalId: v.string(), + displayName: v.string(), + avatarUrl: v.union(v.string(), v.null()), + createdAt: v.number(), + updatedAt: v.number(), + }), + v.null(), + ), handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (identity === null) { @@ -54,7 +69,7 @@ export const me = query({ } return { - id: user._id, + id: user.externalId, externalId: user.externalId, displayName: user.displayName, avatarUrl: user.avatarUrl ?? null, diff --git a/docs/adr/0001-icarus-owns-typed-convex-wrapper.md b/docs/adr/0001-icarus-owns-typed-convex-wrapper.md new file mode 100644 index 00000000..f16867ef --- /dev/null +++ b/docs/adr/0001-icarus-owns-typed-convex-wrapper.md @@ -0,0 +1,265 @@ +# Keep the typed Convex wrapper inside Icarus + +The typed Convex wrapper is an Icarus-owned integration layer. Convex's function +spec is the sole source for its modules, functions, parameters, and structural +result types. Code generation writes the complete Dart API, private structural +wire models, and transport implementation. Those wire models stay inside the +collaboration repositories. Exact tagged payload bindings reuse existing +Icarus types where the server declares their identity. No `dynamic`, `Object?`, +raw map, or stringly typed enum may escape the wrapper, and private decoders +must return either a typed value or a typed decoding failure. + +The generated API uses Dart's pure-interface and redirecting-factory features. +The root client injects its transport through a factory that redirects to the +private generated implementation. Generated module APIs remain pure interfaces +obtained through root getters: + +```dart +abstract interface class IcarusConvexApi { + factory IcarusConvexApi(ConvexTransport transport) = + _$IcarusConvexApi; + + FoldersApi get folders; +} + +abstract interface class FoldersApi { + ConvexQuery> listTree(); +} +``` + +Authored Dart does not repeat ordinary functions, parameters, or result types. +Its only generation configuration binds an opaque, server-declared payload tag +to one existing Icarus type and codec. The generator never matches payloads by +structure or endpoint. Bindings have no wildcard, ordering, fallback, or +endpoint-specific override; a tag has exactly one meaning everywhere it +appears. Missing, extra, or duplicate bindings stop the build. + +For an ordinary closed object validator with no domain tag, the generator emits +a private typed structural model. Repositories map that value into the Icarus +object the app uses. These generated models replace hand-written JSON DTOs such +as `CloudFolderSummary`; their `fromJson` plumbing disappears, and they never +cross the repository boundary. + +App code calls named repository methods. Repository code may build sealed, +typed operation objects internally, but callers do not assemble generic +`StrategyOp` values or choose string route names. + +The generated client covers every public Convex function that a client may +call, including functions Icarus does not call yet. Convex functions meant only +for backend jobs become internal functions instead of appearing in the Dart +API. This full coverage is deliberate: a client-callable function missing from +the generated Dart API is a contract failure, not an allowed undeclared +endpoint. Icarus accepts the extra declarations because they expose accidental +public functions and contract drift before app code depends on them. + +Only the collaboration data layer imports the generated client. Widgets and +providers call hand-written repositories with named domain methods. An +architecture test rejects generated-client imports outside the allowed +directory. + +Public Convex functions accept and return Icarus-owned public IDs. They resolve +those IDs to Convex document IDs on the server, and Convex document IDs remain +inside backend functions. The generator rejects `v.id(...)` in a public +function unless the endpoint has an explicitly approved infrastructure-handle +exception. Existing leaks must be removed before generation: user functions +must stop returning user document IDs, and the image upload handshake must use +an Icarus-owned upload-attempt ID instead of persisting an `imageAssets` ID in +the local queue. Existing Icarus public IDs remain `String` values. + +Generated methods use ordinary nullable named parameters when a Convex field +has only two states: absent or present. The generator introduces a presence +wrapper only when the validator permits three distinct states: absent, `null`, +and a value. + +One-shot calls return `Future` and subscriptions return `Stream`. They +report failures through a sealed exception hierarchy rather than wrapping every +value in a result object. Repositories translate those exceptions into Icarus +sync states. + +Client-side failures have distinct exception types for transport, timeout, and +decoding failures. A function failure carries a generated `ConvexErrorCode` +enum and the server message instead of generating one exception class per +server code. + +`convex/lib/errors.ts` owns one machine-readable error-code catalog. Its +`ErrorCode` type and every structured error helper derive from that catalog, +and the contract snapshot task writes its scrubbed values beside the function +spec. The Dart generator reads that snapshot to emit `ConvexErrorCode`; it does +not scrape throw sites or maintain a second hand-written list. + +Both raw transports must preserve structured Convex error codes and data. The +wrapper never derives a code from a human-readable message. Transport contract +tests prove that native and web produce the same private error value. If +`convex_flutter` cannot expose the structured payload, Icarus extends its fork +before building generated error mapping. + +Subscriptions survive connection loss and authentication refresh. Those states +remain on their existing separate streams. A recoverable function error enters +the typed data stream without closing it. A decoding or contract failure +cancels and closes the subscription because Icarus cannot trust later values +from the same mismatched contract. + +Convex's generated function-spec JSON is the wire contract. The generator walks +every client-callable public function in that spec and emits the entire Dart +API. Generation fails on a missing argument or return validator, an unsupported +validator, `v.any()`, an opaque value without a server discriminator and exact +binding, any missing, extra, or duplicate binding, a non-injective Dart name +conversion, a collision with generated client or `Object` members, or any shape +that would escape as an untyped value. It never auto-suffixes a collision or +guesses a domain type. The generator remains Icarus-owned and only promises to +support Icarus; its internal package layout is not a commitment to publish a +general Convex package. + +The repository commits a scrubbed function-spec snapshot. Developers refresh +it after backend contract changes, and ordinary Dart generation reads the local +snapshot instead of requiring a live Convex deployment. CI also deploys the +current backend to an isolated Convex environment, obtains and scrubs a fresh +function spec, and fails if it differs from the committed snapshot. A stale +snapshot therefore cannot make generation appear clean. + +The repository also commits the generated Dart client. CI regenerates it and +fails when the working tree changes. All output lives as standalone libraries +under `lib/collab/generated/`; authored code never lives there, and generated +files never use `part` or `part of` with authored libraries. Each run recreates +the owned output set so a renamed function cannot leave a stale Dart file. +Developers edit Convex validators or the opaque-payload binding file, never +generated files. + +Canonical element and lineup JSON remains private to the wrapper. Their exact +tag bindings make generated methods accept and return existing typed Icarus +objects at those payload positions. Convex validates each payload's kind, +version, and JSON envelope; golden round-trip tests validate the bound codec's +conversion between that JSON and the Dart domain object. The literal `kind` in +the server validator is the binding key, so every appearance of `drawing` uses +the `DrawingElement` codec and no endpoint may redefine it. If one endpoint +needs different semantics, the server declares a new tag. Icarus does not +duplicate every Flutter object field in TypeScript validators. + +Each generated query method returns a typed query object. Its `fetch()` method +returns `Future` and its `watch()` method returns `Stream`. The endpoint's +arguments and result type are declared once rather than duplicated across +separate fetch and watch declarations. The stream is cold and +single-subscription. Each listener opens one Convex subscription, and canceling +that listener closes it. Generated query objects do not cache or broadcast; +repositories must share a subscription explicitly when several consumers need +the same live result. `fetch()` and `watch()` are independent choices, not two +steps in one read. `fetch()` reads once. `watch()` emits the current result +first and then later changes, so a live consumer does not fetch before it +watches. + +Generated functions take typed named parameters instead of creating an +argument class for every endpoint. The generator serializes those parameters +inside the private implementation. + +The generated client groups functions by their Convex module, such as +`api.folders.listTree()` and `api.strategies.create(...)`, instead of placing +every function on one flat class. The spec path `folders:listTree` +deterministically becomes `api.folders.listTree()`. The generator has no +structural name override or last-write-wins rename table. If two Convex paths +convert to the same Dart identifier, or a path collides with the client API, +generation stops and Icarus renames the backend function while the server is +clay. + +The folder library uses one complete accessible-tree query. The existing +`folders:listAll` and `folders:listForParent` functions become +`folders:listTree`, and repository code shares its single subscription. Root +folders, immediate children, breadcrumbs, owned folders, and shared folders +are derived from that typed tree in Dart. Icarus already needs the complete +tree for its sidebar and path controls, while `listForParent` currently reads +that same tree before filtering it. Keeping both would pay for two reactive +queries without narrowing the server read set. + +The durable queue holds sealed typed op variants in memory. Hive stores each op +and its delivery state as a versioned map containing only primitive values. A +strict decoder reconstructs the sealed variant and reports an unreadable record +without exposing an untyped payload. Outbox encoding is separate from the +generated Convex request encoding, so a server contract change cannot silently +rewrite the on-disk queue format. + +Native and web share the same generated API, codecs, and exception mapping. +That layer targets one small raw transport interface. The native adapter uses +`convex_flutter`; the web adapter uses the Convex JavaScript client. + +Before generated codecs depend on either adapter, both transports normalize +their platform-specific results into one private Convex value model. Native +JSON strings and web JavaScript values never reach generated decoders +directly. A transport conformance suite feeds equivalent fixtures through both +adapters and requires identical normalized results for every supported Convex +value, including numeric boundaries, bytes, nested collections, optional +fields, and nulls. + +Typed object decoders ignore wire fields that their target Dart shape does not +declare, allowing a newer server to add data without breaking an installed +client. They still reject a missing declared field or a value of the wrong +type. + +Generated enums fail decoding when the server sends an unknown literal. Icarus +does not guess at new permission roles, sync statuses, or operation kinds. A +specific harmless enum may opt into an explicit `unknown` case. + +The cloud branch has no users, so protocol version 3 does not carry a converter +for pre-version-3 outbox records. The cutover clears only the branch-owned cloud +outbox and starts with `outboxRecordVersion: 2`; it never clears or rewrites the +local library or `.ica` data. `outboxRecordVersion`, `clientProtocolVersion`, +and each payload's `payloadVersion` remain separate counters. Each changes only +when its own format changes. + +An unknown server error code becomes a `ConvexFunctionException` with an +`unknown` enum value while retaining the raw code and message. The affected +work pauses with that diagnostic; parsing the error never drops the op. + +Before changing the protocol, Icarus fixes queued-op merging so an op ID always +names immutable work. A retry of unchanged work keeps its ID, but any merge +that changes the intended operation receives a new ID. A regression test must +cover the dangerous sequence: Convex applies an op, its response is lost, the +client restores it, the user edits the same entity, and the next flush applies +that newer edit instead of treating it as a replay. This repair ships as a +separate prerequisite rather than hiding inside the protocol rewrite. + +Before wrapper generation begins, Icarus replaces the current generic sync-op +envelope with protocol version 3. The request and acknowledgement contracts +become discriminated unions whose variants declare only the fields they use. +Each request has one closed `type` literal that names exactly one legal +operation. The literal identifies both its entity and action; clients cannot +select those two parts independently and create an illegal pair. Its spelling +uses `.`, such as `element.patch`, because the separator keeps +both parts readable while the complete string remains one discriminator. +Protocol version 3 does not define `element.move` or `lineup.move`. The current +server executes those kinds exactly like `patch`, and the app does not emit +them. Pre-version-3 outbox records containing `move` disappear with the rest of +the development outbox; a distinct `move` returns only if Icarus gives it +distinct behavior. +Each variant also names the record whose revision it protects. It uses fields +such as `expectedPageRevision` and `expectedStrategyRevision` instead of a +generic `expectedRevision` whose meaning changes between operations. +Acknowledgements distinguish newly applied work from an idempotent replay. +An already-recorded op ID returns a first-class `noop` result rather than an +`applied` result carrying `noop` in a reason string. +The result union also separates expected `rejected` outcomes from unexpected +`failed` outcomes. A rejection carries a closed, generated reason that the +sync logic understands, such as a revision mismatch. A failure retains its +structured code and message and moves the affected work to visible attention; +the client never folds it into a rejection by lowercasing or parsing text. +When a revision mismatch causes rejection, that result includes a +variant-specific `current` snapshot containing the guarded record's revision +and typed value. Convex captures the snapshot in the rejecting transaction. +The client does not receive a generic `latestPayload` or issue a second fetch +that could observe a later state. +The redesign keeps the existing sync behavior: page-scoped delivery, +per-record revisions, durable queued work, client and op IDs for replay +protection, batched per-op outcomes, and no-op replay handling. Existing +pre-version-3 outbox records are cleared at the unreleased cutover. The +generator then reads this typed contract instead of preserving the loose +`entityType`, `kind`, optional payload, string reason, and untyped +latest-payload shape. + +The unreleased cloud switches directly to `clientProtocolVersion: 3`; Convex +does not keep a version 2 handler. The cutover wipes the clay deployment and +the development outbox, then rejects any older wire version with a structured +protocol-mismatch failure. + +After protocol version 3 settles and before generator work begins, every public +Convex function receives an explicit return validator. This is a separate +backend milestone because Convex enforces those validators at runtime. Tests +exercise each function against its new validator, and the app must remain green +before generated Dart starts depending on the completed return contract. diff --git a/docs/auth_flow_reference.md b/docs/auth_flow_reference.md index 5e0b1134..486797a5 100644 --- a/docs/auth_flow_reference.md +++ b/docs/auth_flow_reference.md @@ -25,6 +25,9 @@ These are the files that define the current behavior: - `lib/main.dart` - `lib/providers/auth_provider.dart` +- `lib/collab/convex_strategy_repository.dart` +- `lib/collab/generated/` +- `lib/collab/transport/` - `lib/widgets/dialogs/auth/auth_dialog.dart` - `convex/auth.config.ts` - `convex/users.ts` @@ -43,7 +46,7 @@ await ConvexClient.initialize( deploymentUrl: 'https://majestic-eel-413.convex.cloud', clientId: 'dev:majestic-eel-413', operationTimeout: Duration(seconds: 30), - healthCheckQuery: 'health:ping', + healthCheckQuery: defaultConvexHealthCheckQuery, ), ); @@ -308,12 +311,18 @@ Why this exists: ## 9. The app provisions a Convex `users` row after auth is ready -Once Convex says the token is accepted, the app immediately runs: +Once Convex says the token is accepted, the app immediately runs the named +auth boundary: ```dart -await _convexApi.mutation(name: 'users:ensureCurrentUser', args: {}); +await _convexApi.ensureCurrentUser(); ``` +The default auth adapter delegates that method to +`ConvexStrategyRepository.ensureCurrentUser()`. Inside `lib/collab/`, the +repository calls the generated `api.users.ensureCurrentUser()` method. Auth +code does not spell a route or construct a raw argument map. + That mutation does this: ```ts @@ -338,16 +347,17 @@ export const ensureCurrentUser = mutation({ avatarUrl, updatedAt: Date.now(), }); - return existingUser._id; + return { ok: true as const }; } - return await ctx.db.insert("users", { + await ctx.db.insert("users", { externalId, displayName, avatarUrl, createdAt: Date.now(), updatedAt: Date.now(), }); + return { ok: true as const }; }, }); ``` @@ -500,22 +510,24 @@ Why this matters: ## 14. Real cloud queries and mutations depend on this contract -The repository calls Convex functions directly: +The repository is the only application boundary that sees generated structural +types. It calls the typed module and maps the result to the Icarus folder model: ```dart -final response = await _client.query('folders:listForParent', { - if (parentFolderPublicId != null) - 'parentFolderPublicId': parentFolderPublicId, -}); +return _api.folders + .listTree( + scope: const ConvexOptional.present(FoldersListTreeArgsScope.all), + ) + .watch() + .map((folders) => folders.map(_folderEntry).toList(growable: false)); ``` Backend functions enforce auth immediately: ```ts -export const listForParent = query({ - args: { - parentFolderPublicId: v.optional(v.string()), - }, +export const listTree = query({ + args: { scope: folderScopeValidator }, + returns: v.array(folderSummaryValidator), handler: async (ctx, args) => { const user = await requireCurrentUser(ctx); // ... @@ -530,8 +542,10 @@ export const applyBatch = mutation({ args: { strategyPublicId: v.string(), clientId: v.string(), + clientProtocolVersion: v.literal(3), ops: v.array(strategyOpValidator), }, + returns: applyBatchResultValidator, handler: async (ctx, args) => { let strategy = await getStrategyByPublicId(ctx, args.strategyPublicId); await assertStrategyRole(ctx, strategy, "editor"); @@ -560,18 +574,16 @@ export function unauthenticatedError(): ConvexError<{ } ``` -The Flutter client looks for that code in either structured payloads or error strings: +The transport preserves that data as a structured error. The generated client +maps the raw code to `ConvexErrorCode`, and the collaboration boundary checks +the typed values without parsing the message: ```dart -bool isConvexUnauthenticatedError(Object error) { - if (error is Map) { - final code = error['code']?.toString().toUpperCase(); - if (code == 'UNAUTHENTICATED') { - return true; - } - } - - return isConvexUnauthenticatedMessage(error.toString()); +bool isTypedConvexUnauthenticatedError(Object error) { + return (error is ConvexFunctionException && + error.code == ConvexErrorCode.unauthenticated) || + (error is ConvexClientFunctionError && + error.rawCode == ConvexErrorCode.unauthenticated.wireName); } ``` @@ -617,8 +629,8 @@ The flow is stable because each layer has a single responsibility: - **Supabase** proves who the user is and issues JWTs. - **Flutter authProvider** owns session lifecycle, token refresh, deep link handling, and bridge state. - **Convex auth config** teaches Convex how to verify Supabase JWTs. -- `**users:ensureCurrentUser`** converts external identity into an app-level `users` row. -- `**requireCurrentUser` / `assertStrategyRole**` protect actual business data and collaboration rules. +- **`users:ensureCurrentUser`** converts external identity into an app-level `users` row. +- **`requireCurrentUser` / `assertStrategyRole`** protect actual business data and collaboration rules. - **Cloud feature gates** stop the UI from using Convex too early. - **Incident handling** gives recovery behavior when Supabase and Convex drift apart. @@ -714,4 +726,4 @@ The most useful mental model is: - **Convex user row exists** means "the application can attach ownership and permissions to this identity". - **Cloud enabled** means "all three conditions are true enough for the UI to rely on cloud state". -That distinction is the key to understanding the current system. \ No newline at end of file +That distinction is the key to understanding the current system. diff --git a/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_handoff.html b/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_handoff.html new file mode 100644 index 00000000..8e1ee822 --- /dev/null +++ b/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_handoff.html @@ -0,0 +1,767 @@ + + + + + + + + + Rerun the Dart client gauntlet with the missing control + + + + + + + +
+
+
+ + HANDOFF + +
+
+
+ + + +
+ + +
+
+

Icarus cloud · Ready to rerun · Decision reopened

+

Rerun the Dart client gauntlet with the missing control

+

The first gate found a real Dartvex strictness gap, but it did not settle Dartvex versus convex_flutter. Its result-field leg regenerated the same fixture, whose return contract was null, instead of testing an actual result rename. A diagnostic control with an explicit return schema did catch the rename at analysis time. The fair next step is to make the server contract explicit, add a thin fail-closed Icarus wrapper, and then run the same runtime faults against both clients.

+ +
+

Decision now

+ +
+
1missing result mutation
+
0runtime seeds completed
+
3analyzer exit on real rename
+
50rerun seeds required
+
+ +

No client winner yet. Keep the first gate as evidence that Dartvex 0.2.0 is not fail-closed by default. Reopen the runtime decision because neither client has completed the symmetric chaos test.

+ +

The goal is still less JSON plumbing and stronger generated Dart APIs. The first run shows that Dartvex can supply useful function and argument types, but Icarus must enforce complete public return validators and reject degraded output. That compensation is small enough to test before writing a generator or forking another package.

+
+ +
+

What the first gate proved

+ +
+ + + + + + + + + + + +
CheckObservedMeaningStatus
Function renameOld method failed analysis with exit 3Generated function names protect callersproved
Argument renameOld named argument failed analysis with exit 3Generated arguments protect callersproved
Result-field renameBaseline fixture regenerated; returns stayed nullNo result mutation was exercisedinvalid leg
Missing return schemaDartvex emitted Future<dynamic>Unspecified server output cannot become a typed Dart resultreal gap
Unknown validatorWarning, exit 0, field degraded to dynamicDefault generation is not fail-closedreal gap
DeterminismSecond baseline generation produced no diffSame input generated the same outputproved
Runtime chaos and profileSkipped by the original stop ruleNo correctness, latency, memory, reconnect, or auth comparison existsnot run
+
+

Evidence: contract_gate.dart, baseline function spec, contract_gate.json, and the first result note.

+ +

The exact fairness failure

+

In contract_gate.dart, the result-field leg calls generate('folders_list_for_parent.json') again. The fixture contains "returns": null. The real folders:listForParent query also has no explicit returns: validator. The old caller therefore remained valid because there was no typed result contract to rename.

+ +

A diagnostic control supplied an explicit object return, generated the typed caller, then changed publicId to folderPublicId. Baseline analysis exited 0; analysis against the renamed result exited 3 with an undefined getter. That does not make Dartvex the winner. It proves the missing control can reverse the narrow conclusion about result-drift detection.

+
+ +
+

The neutral test boundary

+ + + +

Icarus continues to own the outbox, clientId/opId identity, revision and conflict rules, canonical JSON, and .ica round-trip. A client package is transport and tooling, not the owner of those promises. This is also why adopting either package unchanged would be the wrong abstraction boundary.

+

Before touching server sync boundaries, read server_side_sync_boundaries_handoff.md.

+
+ +
+

Fair rerun plan

+ +

Phase 1: repair the contract gate

+
    +
  1. Add explicit returns: validators to the stable public Convex functions used by the comparison. Match the real payload exactly; do not create a test-only fantasy type.
  2. +
  3. Regenerate a scrubbed baseline fixture and add a separate result-renamed fixture where publicId becomes folderPublicId.
  4. +
  5. Compile the unchanged caller against both. Baseline must exit 0. The renamed fixture must exit nonzero and identify the old getter.
  6. +
  7. Add an Icarus-owned wrapper around Dartvex generation. It fails if generation logs Warning:, if a stable public module contains unexpected dynamic, or if the generator exits nonzero.
  8. +
  9. Keep the unknown-validator mutation. It must fail the wrapper with the function and field path.
  10. +
  11. Run generation twice and require a clean repository diff.
  12. +
+

Boundary: do not fork Dartvex or write a replacement generator in this phase. First prove whether explicit server schemas plus a thin strict wrapper deliver the typed API we want.

+ +

Phase 2: make the runtime comparison symmetric

+

Define one small typed Icarus transport interface for the operations used by the gauntlet. Implement it once with Dartvex and once with convex_flutter. Both adapters must receive the same serialized operations, tokens, reconnect schedule, timeouts, and deployment. Package-specific convenience APIs cannot change the workload.

+

Keep two scorecards. The tooling score covers generated coverage, compile-time mutation catches, determinism, warnings, diff size, and maintenance. The runtime score covers correctness, convergence, recovery, latency, CPU, memory, and platform builds. A tooling loss cannot masquerade as a runtime loss, and a fast runtime cannot excuse corrupted library state.

+ +

Phase 3: run correctness before performance

+
    +
  • Run 50 deterministic seeds × 1,000 operations for each adapter.
  • +
  • Use editors A and B plus a clean verifier C. Begin every seed from base-test-v43.ica.
  • +
  • Exercise offline edits, delayed and duplicated delivery, reconnect, subscription restart, delete/recreate, revision conflict, and bounded retries.
  • +
  • Exercise an expired or rejected access token, call the current Supabase Flutter refreshSession() path, reconnect, and prove the queued op lands exactly once.
  • +
  • Persist the runner ledger and reuse identical clientId/opId values for both adapters so a process restart does not give one client an easier test.
  • +
  • After every seed, verifier C exports canonical state. Compare strategies, pages, folders, lineups, order, revisions, and round-trip output. Never compare timestamps or transport-only metadata that the product does not promise.
  • +
+

Use disposable test accounts and publishable client credentials. Never put a Supabase secret or service_role key in Flutter, fixtures, committed output, or logs. The current Supabase Dart API documents refreshSession() as refreshing and returning a new session even when the current session is not expired; the fault injector should assert the session actually changes or is accepted before replaying the op.

+ +

Phase 4: profile only after both are correct

+

Run at least 10 paired profile-build trials, alternating which adapter runs first. Report median and p95 remote convergence, reconnect-to-live time, peak RSS, steady-state CPU, transferred bytes, and build size on every supported desktop target. Record raw samples, tool versions, machine state, commit, and deployment identity.

+
+ +
+

What settles the gauntlet

+ +
+ + + + + + + + + +
ConditionDartvex consequenceconvex_flutter consequence
Any dropped, duplicated, misordered, or silently conflicted library changeImmediate loss, regardless of speed
Cannot recover queued work after auth refresh or reconnectImmediate loss
Generated stable API contains unexpected dynamicLoss unless the thin strict gate rejects it before commitNot a generated-code criterion
Both complete all 50 seeds with canonical equalityCompare profile results, API clarity, adapter size, dependency health, and maintenance cost
Runtime is tied within measurement noiseWins if the generated boundary materially removes JSON plumbingWins if Dartvex still needs broad custom generation or fragile patches
+
+ +

The expected best outcome is not “Dartvex untouched.” It is Dartvex plus a narrow Icarus strictness policy. If that produces complete, deterministic types and passes the same runtime gauntlet, Dartvex earns the win because it moves contract failures into analysis and removes hand-written JSON decoding. If the compensation grows into a package fork, a second generator, or recurring patches for common Convex validators, convex_flutter remains the more honest base.

+
+ +
+

Remote machine handoff

+ +

Start here

+
    +
  1. Check out t3code/convex-client-gauntlet and pull the latest commit.
  2. +
  3. Read this handoff, the first result note, and the server sync boundary handoff.
  4. +
  5. Reproduce the committed first gate before changing fixtures.
  6. +
  7. Implement Phase 1 as a distinct commit. Do not begin runtime work until every repaired contract check is green.
  8. +
  9. Implement the neutral interface and two adapters without changing local Hive models, .ica, UI, outbox semantics, revision rules, or server payload semantics.
  10. +
  11. Run correctness, then profile. Commit raw machine-readable results and a short human verdict. Leave the old result file intact as historical evidence.
  12. +
+ +

Current commands that exist

+
git switch t3code/convex-client-gauntlet
+git pull --ff-only
+
+cd tool/convex_client_gauntlet
+fvm dart pub get
+fvm dart run bin/run.dart
+fvm dart test
+fvm dart analyze
+
+cd ../..
+npx tsc --noEmit
+npm run test:convex
+fvm flutter test
+fvm flutter analyze --no-fatal-infos
+fvm flutter build web --no-tree-shake-icons
+

These reproduce the committed first gate and repository baseline. Add named contract-v2, runtime, and profile entry points as part of the rerun; document their exact commands beside the resulting artifacts rather than pretending they already exist.

+ +

Required artifacts from the rerun

+
    +
  • Explicit-return baseline and actual result-renamed fixtures.
  • +
  • A strict wrapper test proving warnings and unexpected dynamic fail with a useful path.
  • +
  • Generated-output snapshots or hashes proving determinism.
  • +
  • Per-seed runtime JSON for both adapters, including fault schedule and canonical verifier hash.
  • +
  • Paired profile samples with run order, machine, build mode, and package versions.
  • +
  • A final matrix that distinguishes compile-time safety, runtime correctness, performance, and maintenance.
  • +
+
+ +
+

Acceptance checklist

+
    +
  • The result rename mutates a return field, not the function name, argument, caller, or baseline fixture.
  • +
  • The stable public Convex functions in scope have explicit return validators that match real payloads.
  • +
  • Dartvex baseline generation is warning-free and contains no unexpected dynamic.
  • +
  • The unchanged caller fails analysis for function, argument, and result renames.
  • +
  • Unsupported validators fail before generated code can be committed.
  • +
  • Both adapters receive byte-for-byte equivalent operation traces and fault schedules.
  • +
  • All 50 seeds end with exact canonical equality and no unresolved op.
  • +
  • Auth refresh is performed with the client session only; no elevated credential appears anywhere.
  • +
  • All exported strategies and library backups still round-trip.
  • +
  • No winner is declared from the tooling gate alone.
  • +
+
+ +
+

Appendix

+
+ Raw fairness evidence · first gate and diagnostic control +
Committed first gate
+  result-field step: generate('folders_list_for_parent.json')
+  baseline fixture: "returns": null
+  runtime seeds: 0
+
+Diagnostic missing control
+  explicit typed baseline analysis: exit 0
+  publicId -> folderPublicId analysis: exit 3
+  failure: undefined getter on the unchanged caller
+
+Correct interpretation
+  Dartvex 0.2.0 is not fail-closed by default.
+  The original test did not compare runtime clients.
+  A complete result schema lets the generated caller catch result drift.
+

Captured 2026-08-26 against branch base df9f1934bbc7ab71e144208036f385b8f73c77aa. The diagnostic fixture was intentionally not committed; the fair implementation must add a reviewed equivalent.

+
+
+ +

Generated 2026-08-26 · Icarus cloud client evaluation · base df9f193 · Dartvex 0.2.0 · convex_flutter 3.0.1 · Supabase Flutter auth guidance checked 2026-08-26 · revision v1

+
+
+
+ + + + diff --git a/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_result.md b/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_result.md new file mode 100644 index 00000000..b268d02a --- /dev/null +++ b/docs/cloud_sync_refactor/convex_dart_client_fair_rerun_result.md @@ -0,0 +1,185 @@ +# Convex Dart client fair rerun result + +Status: complete on 2026-08-27 + +Harness and repair commit: `fb83488c0924f8daf57c4bdfc48d7a4a5ff0c8f5` + +Candidates: Dartvex 0.2.0 and `convex_flutter` 3.0.1 with the Icarus auth and +reconnect repair + +Decision: keep `convex_flutter`; do not migrate to Dartvex. + +## Verdict + +The earlier “Dartvex wins” statement was not a fair final verdict. Dartvex had +passed while `convex_flutter` was blocked by a package auth defect, so the run +had identified a broken candidate rather than measured two valid candidates. + +That defect is now fixed at the package and Rust-client layers. Both candidates +completed all 50 deterministic seeds and all 50,000 ops. Each recorded 45,500 +landed ops, 4,500 planned visible revision rejects, zero unresolved ops, exact +once-only replay after auth refresh, durable checkpoint recovery, 50 matching +canonical verifier hashes, and 50 successful `.ica` round-trips. + +There is therefore no correctness winner. In the valid paired profile, +`convex_flutter` was 31.9% faster by median runner wall time and 34.0% faster on +real reconnect-to-live time. Dartvex used 5.1% less peak RSS, 36.4% less CPU in +relative terms (5.29 percentage points), and 12.9% less transfer than +`convex_flutter` (14.9% more when expressed from the Dartvex baseline). Remote +convergence differed by only 0.156 ms at the median. + +Those are trade-offs, not a blanket performance winner. Icarus should keep its +current client because Dartvex's stable generated return surface is still +incomplete for the runtime functions, so migrating would not yet remove the +path-and-JSON boundary that motivated the evaluation. The cost is explicit: +the current repair vendors both `convex_flutter` and Convex Rust 0.10.4 until +equivalent fixes are published upstream. + +## Auth repair + +The failure had four interacting causes: + +- the published Flutter adapter owned a separate token-expiry timer and fed the + Rust client static auth, outside the state machine that replays auth, + subscriptions, and in-flight mutations after reconnect; +- disposing an old refresh handle could asynchronously clear a newer auth + callback; +- the public native `reconnect()` method was an authenticated health query, not + a socket transition; +- Convex Rust applied independent client-worker and WebSocket network backoffs + to one auth rejection. A fresh token could sit behind a 15-second backoff, + while stale responses and connection metadata could start further protocol + recovery loops. + +The repair gives the token callback to the upstream Convex state machine, adds +generation ownership to auth handles, implements a real reconnect that waits +for connecting then connected, and patches Convex Rust to preserve one session +identity with monotonic connection counts. Auth rejection now uses a bounded +250 ms callback cadence, coordinates that state with the WebSocket worker so it +does not add a second network backoff, discards responses from the replaced +socket, and only exits auth recovery after a changed token receives a valid +server response. + +Ten consecutive debug calibration runs passed before the authoritative rerun. +In the final profile samples, `convex_flutter` accepted the refreshed token in +73.848–171.027 ms; all queued work landed exactly once. + +## Fair workload + +Both adapters used the same isolated local Convex deployment, disposable client +account, public Supabase anon credential, base fixture, serialized op traces, +IDs, timeouts, and fault schedules. The seed-0 trace and schedule hashes match: + +- trace: `ddf6d41ed9ccdbf3c60766fe6b0318218dd8954d8615fcffb2a8daefe849aa06` +- fault schedule: + `b4e244c38b89789f1191e988768aa681f4722184533077ef58eb6f95ee6a0e52` +- base fixture: + `test/fixtures/strategy_integrity/base-test-v43.ica`, SHA-256 + `8544873d608a0ad885b2e6042a383596a0b1dc37514034281b4e4eec6168756a` + +The 1,000-op trace covers strategy, page, page content, element, and lineup +changes, including add, patch, reorder, delete/recreate, duplicate delivery, +revision conflicts, subscription restart, real reconnect, offline delay, auth +rejection/refresh, and durable process restart. A clean Dartvex client acts as +verifier C. Canonical state excludes server-authored transport clocks. + +## Correctness + +| Result | Dartvex | `convex_flutter` repaired | +| --- | ---: | ---: | +| Seeds | 50/50 | 50/50 | +| Operations | 50,000/50,000 | 50,000/50,000 | +| Landed | 45,500 | 45,500 | +| Planned visible rejects | 4,500 | 4,500 | +| Unresolved | 0 | 0 | +| Refreshed token accepted | yes | yes | +| Queued auth-fault batch landed once | yes | yes | +| Persisted checkpoint resumed | yes | yes | +| Canonical verifier and `.ica` round-trip | 50/50 | 50/50 | + +The debug correctness reports recorded 64,346.843 ms total runner wall time and +256,311,296 bytes maximum RSS for Dartvex, versus 74,446.106 ms and 266,141,696 +bytes for `convex_flutter`. These prove completion but are not used as the +performance comparison; the paired profile build below is authoritative for +that. + +## Paired macOS profile + +Ten paired profile-build trials were run per candidate, alternating which +candidate ran first and replacing deployment data before every run. CPU is +defined as process user plus system seconds divided by the runner's wall-clock +window. P95 uses nearest rank, so with ten samples it is the maximum observed +sample. + +| Metric | Dartvex median / p95 | `convex_flutter` median / p95 | +| --- | ---: | ---: | +| Remote convergence | 7.304 / 7.804 ms | 7.459 / 7.575 ms | +| Reconnect to live | 110.893 / 150.191 ms | 73.243 / 94.765 ms | +| Fresh token accepted | 9.144 / 9.662 ms | 127.134 / 171.027 ms | +| Full auth recovery | 147.883 / 166.973 ms | 205.734 / 543.221 ms | +| Runner wall time | 2,749.544 / 3,396.189 ms | 1,873.387 / 2,312.830 ms | +| Peak RSS | 134,406,144 / 134,856,704 B | 141,312,000 / 141,541,376 B | +| Average process CPU | 9.248% / 10.118% | 14.533% / 15.306% | +| Application JSON transfer | 1,439,001 / 1,439,001 B | 1,652,716 / 1,771,501 B | + +The shared universal macOS harness bundle is 78,540,800 bytes and contains both +adapters, so candidate-isolated bundle size is not available. The +`convex_flutter` native framework executable is 23,195,280 bytes; the shared App +framework executable is 8,000,528 bytes. The build contains arm64 and x86_64 +slices and ran on an arm64 Mac. Windows and Linux remain package-supported but +could not be built or measured from this macOS host; they are recorded as +unmeasured rather than silently generalized. + +## Separate scorecards + +| Area | Dartvex 0.2.0 | `convex_flutter` repaired | +| --- | --- | --- | +| Runtime correctness | pass | pass | +| Rejected-token recovery | pass | pass after package/Rust repair | +| Real reconnect | pass | pass after replacing health-query implementation | +| Median wall time | slower | faster | +| CPU, RSS, transfer | lower | higher | +| Generated contract boundary | strict wrapper passes, runtime return surface incomplete | none; hand-written JSON boundary | +| Maintenance | pure Dart package plus local strict gate | vendored Flutter package, Rust crate, generated bridge, and pinned FRB 2.11.1 | + +## Artifacts + +- [`contract_gate.json`](../../tool/convex_client_gauntlet/results/contract_gate.json) +- [`dartvex_correctness.json`](../../tool/convex_client_gauntlet/runtime/results/dartvex_correctness.json) +- [`convex_flutter_correctness.json`](../../tool/convex_client_gauntlet/runtime/results/convex_flutter_correctness.json) +- [`paired_profile_macos.json`](../../tool/convex_client_gauntlet/runtime/results/paired_profile_macos.json) +- [`fair_rerun_matrix.json`](../../tool/convex_client_gauntlet/runtime/results/fair_rerun_matrix.json) +- [runtime commands](../../tool/convex_client_gauntlet/runtime/README.md) +- [`convex_flutter` patch notes](../../third_party/convex_flutter/ICARUS_PATCH.md) +- [Convex Rust patch notes](../../third_party/convex_rs/ICARUS_PATCH.md) + +The raw artifacts contain no email, password, access token, refresh token, +Supabase key, Convex admin key, or elevated credential. The deployment was the +isolated local instance at `127.0.0.1:3210`; no production or user library data +was used. + +## Verification + +- The contract gate passed all six checks; its two regression tests and Dart + analysis passed. +- The runtime harness passed four workload tests, with the environment-gated + transport smoke test skipped, and both the runtime and nested app analyzed + cleanly. +- The Icarus suite passed all 343 tests, including all 15 auth-provider tests. + TypeScript analysis passed and the 22 Convex boundary tests passed. +- The Icarus app analyzed with six pre-existing info lints and no warning or + error. Web and macOS release builds passed with icon tree shaking disabled; + the macOS app bundle was 97.8 MB. +- Convex Rust passed all 36 tests. The native Flutter package passed Cargo + check/test, and its Dart `lib/` analyzed with no warning or error. +- All four checked-in JSON artifacts parse, their recorded SHA-256 values + match, the final diff has no whitespace errors, and the artifact secret scan + passed. + +## Next step + +Keep `convex_flutter`, upstream the package and Convex Rust repairs, and remove +the local vendors when published releases pass this same gate. Dartvex can be +reconsidered after its stable generated return surface covers the actual Icarus +runtime boundary and proves that a migration removes JSON plumbing instead of +moving it into another wrapper. diff --git a/docs/cloud_sync_refactor/convex_dart_client_gauntlet_result.md b/docs/cloud_sync_refactor/convex_dart_client_gauntlet_result.md new file mode 100644 index 00000000..e123e867 --- /dev/null +++ b/docs/cloud_sync_refactor/convex_dart_client_gauntlet_result.md @@ -0,0 +1,120 @@ +# Convex Dart client gauntlet result + +Status: first gate recorded; runtime decision reopened on 2026-08-26 + +Base: `origin/icarus-cloud` at +`e59402eedee9035cf14693fbd26fe8b097d6abfa` on 2026-08-26 + +Candidate versions: `dartvex` 0.2.0 and `dartvex_codegen` 0.2.0 + +Decision: no client winner yet + +## Fairness correction + +The first gate found a real fail-open strictness gap in Dartvex 0.2.0, but its +result-field leg regenerated the baseline fixture instead of exercising a +renamed result fixture. Because that baseline declares `returns: null`, it +could not test whether a typed generated caller catches result drift. + +A diagnostic control with an explicit object return passed baseline analysis +and then failed analysis with exit 3 after `publicId` was renamed to +`folderPublicId`. The runtime comparison still has not run. The corrected, +authoritative next-step plan is +[convex_dart_client_fair_rerun_handoff.html](convex_dart_client_fair_rerun_handoff.html). + +## Result + +The original run stopped at the compile-time contract gate and therefore did +not run the runtime chaos or profile stages. Treat that stop as a recorded +strictness finding, not as a final package verdict. + +The stable `folders:listForParent` function declares argument validators but +no `returns:` validator. Convex represents that result as `returns: null` in a +function spec. Dartvex 0.2.0 deliberately maps the absent result contract to +`Future`. A caller that reads `result.first.publicId` still passes Dart +analysis. The committed runner did not actually rename a result field, so this +observation does not establish whether a complete generated return type catches +that change. + +Dartvex also treats an unknown validator as a warning and generates the +affected field as `dynamic`. Generation exits zero instead of stopping at the +function and field path. + +These are declared losing conditions in the comparison plan. They are not +performance observations and cannot be averaged away. + +| Contract check | Required | Observed | Result | +| --- | --- | --- | --- | +| Function rename | Old method fails analysis | Analysis exits 3 | Pass | +| Argument rename | Old named argument fails analysis | Analysis exits 3 | Pass | +| Result-field rename | Old field access fails analysis | No result rename was exercised; baseline return is `dynamic`; analysis exits 0 | **Invalid leg** | +| Unsupported validator | Generation exits nonzero with a path | Warning with path; generation exits 0 and emits `dynamic` | **Fail** | +| Second generation | No repository diff | No generated-file change | Pass | + +## Reproduce + +The evaluation lives in an isolated Dart package so the rejected candidate is +not added to the Icarus application or its lockfile. + +```bash +cd tool/convex_client_gauntlet +fvm dart pub get +fvm dart run bin/run.dart +fvm dart test +fvm dart analyze +``` + +The committed machine-readable result is +[`tool/convex_client_gauntlet/results/contract_gate.json`](../../tool/convex_client_gauntlet/results/contract_gate.json). +The runner regenerates bindings for the baseline, function-rename, +argument-rename, and unsupported-validator fixtures and compiles the same old +caller after each relevant change. + +## Runtime stage + +Skipped by the comparison's explicit stop rule: + +> If Dartvex generates function names but leaves public results as `dynamic`, +> stop the runtime comparison and record that gap before writing new generator +> code. + +Consequently, this result makes no claim about Dartvex runtime correctness, +latency, memory, reconnect behavior, auth recovery, or platform builds. The +recorded counts are zero because the runtime workload was not started, not +because either client completed it without faults. + +No client abstraction, adapter, application migration, custom generator, +production deployment, local Hive model, `.ica` format, outbox, revision rule, +or server payload changed in this comparison. + +## Refreshed baseline + +Before the gate, the refreshed cloud base passed: + +- `npm ci` (with the existing npm audit report of 4 dependency + vulnerabilities: 2 moderate, 1 high, 1 critical) +- `npx tsc --noEmit` +- `npm run test:convex` (22 tests) +- the six focused Flutter files named by the two handoffs (82 tests) + +After recording the decision, the comparison branch passed: + +- `fvm dart test` in `tool/convex_client_gauntlet` (1 test) +- `fvm dart analyze` in `tool/convex_client_gauntlet` (no issues) +- `npx tsc --noEmit` +- `npm run test:convex` (22 tests) +- `fvm flutter test` (343 tests) +- `fvm flutter analyze --no-fatal-infos` (exit 0 with the same 6 + pre-existing info-level lints) +- `fvm flutter build web --no-tree-shake-icons` + +The exact `fvm flutter build web` command still fails on the refreshed base's +three existing non-constant `IconData` sites in `folder_provider.dart`, +`hive_adapters.g.dart`, and `archive_manifest.dart`. This comparison does not +change those files. Disabling icon tree shaking proves the web target otherwise +compiles; the pre-existing release-build cleanup remains separate work. + +The next comparison may add explicit public result validators and a thin, +Icarus-owned strict wrapper that rejects warnings and unexpected `dynamic`. +Writing a replacement generator or package fork remains out of scope until that +smaller compensation is tested. diff --git a/docs/cloud_sync_refactor/typed_convex_wrapper_implementation_handoff.html b/docs/cloud_sync_refactor/typed_convex_wrapper_implementation_handoff.html new file mode 100644 index 00000000..2dbbfbee --- /dev/null +++ b/docs/cloud_sync_refactor/typed_convex_wrapper_implementation_handoff.html @@ -0,0 +1,926 @@ + + + + + + + + + Build the typed Convex wrapper in eight gated phases + + + + + + +
+
+
+ + PLAN + +
+
+
+ +
+ +
+
+

Handoff · Accepted · Plan only

+

Build the typed Convex wrapper in eight gated phases

+

Icarus will keep convex_flutter as its native runtime and own the typed layer above it. The implementation starts by repairing op identity, then makes protocol version 3 and every public return shape explicit before generating Dart. The target removes manual JSON plumbing, closes the native and web transport gap, and reduces folder reads from 2 live subscriptions to 1. This document authorizes planning and implementation on the remote machine, but it records no completed wrapper work.

+
+

Executive summary

+
+
8ordered phases
+
45public functions now
+
1 / 45return validators now
+
2 → 1folder subscriptions
+
+

Source: rg over convex/*.ts and the active Riverpod call sites at base commit be63971, captured 2026-08-29.

+

Build contract first. No generator work begins until op IDs name immutable work, protocol version 3 is closed and typed, and every client-callable Convex function has explicit argument and return validators.

+

The architecture is settled. The remote agent should make implementation choices only inside the boundaries below. If a boundary proves impossible, stop with a failing fixture and the smallest source trace that proves why. Do not weaken a type to keep moving.

+

Confidence is high for the protocol, generator, repository, and folder-tree shape. Confidence is medium for structured native errors and the exact function-spec scrubbing command until Phase 0 proves both against the pinned packages.

+
+
+

Read this before editing

+
    +
  1. Read ADR 0001. It owns architecture decisions. This handoff owns order and completion gates.
  2. +
  3. Read CONTEXT.md, AGENTS.md, and convex/_generated/ai/guidelines.md. Use the existing Icarus vocabulary.
  4. +
  5. Inspect git status before each phase. Preserve unrelated work and stage only the files named by that phase.
  6. +
  7. Edit source validators and models. Regenerate *.g.dart and convex/_generated/; never edit generated output by hand.
  8. +
  9. Keep each phase green before starting the next. A red completion gate starts the repair loop below.
  10. +
+
# Baseline checks before Phase 0
+git status --short
+git rev-parse HEAD
+fvm flutter pub get
+npm ci
+npx tsc --noEmit
+npm run test:convex
+fvm flutter analyze --no-fatal-infos
+fvm flutter test
+

The repository pins Flutter through .fvmrc. Use FVM for Dart and Flutter commands.

+

Use the dedicated test account

+

This account exists only for Icarus verification. Its values are intentionally committed in this handoff so the remote agent can run the complete test without another setup step.

+
# Icarus test auth
+ICARUS_E2E_EMAIL=shawnadedeji@gmail.com
+ICARUS_E2E_PASSWORD=testpassword123
+
    +
  • These are disposable test credentials. Their presence in Git and the public HTML is intentional.
  • +
  • Computer Use may read the values from this block or from matching environment variables and enter them directly into the Icarus email and password fields. No further confirmation is needed.
  • +
  • Use this account only against the Icarus test Supabase project and its Convex deployment.
  • +
  • Take the first screenshot after the auth dialog closes so the receipt proves the signed-in product state.
  • +
+

A red gate means fix, prove, and continue

+
    +
  1. Freeze the smallest reproducible state. Save the seed, operation trace, failing fixture, logs, screenshots, platform, deployment, and commit that identify the failure.
  2. +
  3. Find the root cause. Fix it inside the accepted architecture. Do not weaken a type, skip a platform, loosen a comparison, or delete the failing fixture.
  4. +
  5. Add the smallest permanent regression test that goes red on the captured failure and green on the fix.
  6. +
  7. Run the focused test, the current phase gate, and every earlier gate the fix could affect. Rebuild before repeating a visible product check.
  8. +
  9. Resume from the earliest Computer Use step whose assumptions changed. Continue through the remaining phases and final matrix.
  10. +
+

done The issue is fixed only when its regression test passes, the affected broad gates pass, and the original reproduction no longer fails. Evidence is the start of the repair, not the end of the task.

+

Non-goals are equally firm. Do not replace Convex, adopt Dartvex, migrate to SpacetimeDB, redesign the UI, publish a general Dart package, change the local library schema, or change the .ica format. The cloud deployment and development outbox may be wiped because this branch has no cloud users. The local Hive library may not be wiped.

+
+
+

Fixed contract

+
+ + + + + + + + + + + + + + + + +
AreaAccepted shapeRejected shortcut
OwnershipIcarus owns the generator, generated API, codecs, and transport adapters.Depending on another package to define Icarus's public Dart contract.
TypingNo dynamic, Object?, raw map, or string enum crosses the wrapper boundary.Falling back to unknown types when generation cannot map a validator.
CoverageGenerate every client-callable public Convex function. Convert backend-only jobs to internal functions.Generating only the routes used today.
IDsPublic functions use Icarus public IDs as Dart String values. Convex document IDs stay on the server.Leaking v.id(...) because both representations happen to be strings.
ResultsClosed structural validators generate endpoint-scoped typed models. Repositories map them to Icarus models.Structural guessing or automatic cross-endpoint type deduplication.
PayloadsExact server tags bind canonical JSON envelopes to existing Icarus objects through annotated codecs.Wildcards, endpoint overrides, last-write-wins bindings, or duplicating every Flutter field in TypeScript.
QueriesOne ConvexQuery<T> exposes independent fetch() and watch(). A watch emits current data first.Fetching before watching or generating duplicate methods.
ErrorsTransport, timeout, decoding, and function failures stay distinct. Function errors preserve structured code, raw code, and message.Parsing human-readable error text.
OpsProtocol version 3 uses one closed discriminator such as element.patch. Known conflicts are rejected; unexpected failures are failed.Independent entity and action strings that can form illegal pairs.
OutboxSealed typed ops in memory, versioned primitive maps in Hive, strict reconstruction on load.Persisting generated request objects or assuming Hive object storage removes the wire format.
FoldersOne shared folders:listTree watch. Dart derives roots, children, paths, owned folders, and shared folders.A second parent query that rereads the same accessible tree.
DeliveryCommit the scrubbed function spec and generated Dart. CI regenerates both and rejects drift.Requiring a developer deployment for ordinary generation or trusting stale generated files.
+
+

Source: ADR 0001 and the accepted design review through 2026-08-29.

+
+
+

Target Dart shape

+

Authored Dart declares only domain codecs. The generator owns modules, methods, named parameters, enums, ordinary result models, serialization, and the private implementation.

+
# Authored binding syntax in lib/collab/convex_payload_codecs.dart
+@ConvexPayload('drawing')
+final class DrawingElementConvexCodec
+    implements ConvexPayloadCodec<DrawingElement> {
+  const DrawingElementConvexCodec();
+  @override
+  ConvexValue encode(DrawingElement value) => /* canonical envelope */;
+  @override
+  DrawingElement decode(ConvexValue value) => /* strict decoder */;
+}
+# Generated use inside a repository
+final query = api.folders.listTree();
+final currentTree = await query.fetch();
+final liveTree = query.watch();
+

The annotation plus generic codec follows normal Dart generator design. There is no runtime registration list. The generator scans this one configured library and fails on a missing, unused, or duplicate tag.

+
# Target source ownership
+convex/
+  function_spec.json                 # scrubbed and committed
+  error_codes.json                   # scrubbed server error catalog
+  lib/opTypes.ts                     # protocol v3 validators
+lib/collab/
+  convex_payload_codecs.dart         # authored exact tag bindings
+  generated/                         # generated standalone libraries only
+  transport/                         # normalized native and web adapters
+  repositories/                      # named Icarus methods and domain mapping
+tool/icarus_convex_codegen/
+  bin/generate.dart                  # deterministic Icarus-owned generator
+  lib/                               # parser, mapper, emitter, contract checks
+  test/                              # fixtures and golden failures
+ +

Source: target ownership and dependency direction from ADR 0001.

+
+
+

Execution order

+

Phase 0: prove the two uncertain seams

+

Keep this phase small. It produces fixtures and commands, not production abstractions.

+
    +
  • Run npx convex function-spec against the pinned Convex CLI. Capture stdout, identify deployment-specific fields, and prove that scrubbing then sorting produces byte-identical output twice.
  • +
  • Add a fixture with nested objects, unions, literals, optional fields, nulls, bytes, numbers, and a structured ConvexError.
  • +
  • Pass the fixture through the current native and web clients. Record the exact value and error data each transport exposes.
  • +
  • Keep the earlier Dartvex contract gate as evidence and fixture material. Do not add Dartvex to the app.
  • +
+

exit A committed fixture proves the function spec contains complete validators. A transport test either proves structured error parity or fails with the exact missing native or web field. The failure becomes a scoped fork task in Phase 5.

+

Phase 1: make op IDs immutable

+

Fix _mergeQueuedIntent before changing the protocol. The current implementation reuses existing.opId after payload, kind, target page, sort order, or expected revision changes.

+
    +
  • Keep an op ID only when _sameIntent says the work is unchanged.
  • +
  • Create a new UUID before persisting any merge that changes intended work. Preserve the client ID.
  • +
  • Write the replacement record durably before exposing it as queued state.
  • +
  • Cover add plus patch coalescing, patch replacement, reorder replacement, delete cancellation, restore, rejected retry, and process restart.
  • +
  • Add the lost-response regression: Convex applies op A, the response disappears, the client restores A, the user creates work B for the same entity, and the next flush applies B instead of replaying A.
  • +
+

exit No test permits changed work to retain an earlier op ID. Retrying byte-for-byte equivalent work keeps its ID. All outbox tests pass independently.

+

Phase 2: replace the generic op envelope with protocol version 3

+
    +
  • Replace kind plus entityType with a discriminated union keyed by one type literal such as element.patch.
  • +
  • Give every variant only its legal fields. Use names such as expectedPageRevision and expectedStrategyRevision when the guarded record differs.
  • +
  • Keep patch for element and lineup movement. Remove element.move and lineup.move. Retain a separate reorder operation only where ordering has distinct behavior.
  • +
  • Return a closed result union: applied, noop, rejected, or failed. Rejections carry typed reasons and an in-transaction typed current snapshot when needed.
  • +
  • Set clientProtocolVersion to 3. Set the new Hive outboxRecordVersion independently. Clear the development outbox and wipe the clay Convex deployment. Do not add a version 2 handler or pre-version-3 converter.
  • +
+

exit TypeScript cannot construct an illegal entity/action pair. Dart queue code exhaustively handles every request and result variant. Protocol mismatch is a structured failure. Convex and Dart protocol tests pass.

+

Phase 3: close the public Convex contract

+
    +
  • Audit all 45 current public functions. Convert backend-only maintenance and sweep functions to internal functions.
  • +
  • Add explicit argument and return validators to every remaining client-callable function. Use closed literals and unions for roles, statuses, result kinds, and error reasons.
  • +
  • Remove public v.id(...) values. Replace the image upload handshake with an Icarus upload-attempt public ID. Record any unavoidable infrastructure-handle exception explicitly in the generator allowlist and its test.
  • +
  • Change convex/lib/errors.ts to export one errorCodes constant and derive its ErrorCode type from that constant. Make every structured error helper accept only that type. The snapshot task writes the catalog to convex/error_codes.json.
  • +
  • Rename folders:listAll to folders:listTree. Delete folders:listForParent. The tree result includes the visible parent public ID and typed access role needed for local derivation.
  • +
  • Commit scrubbed convex/function_spec.json and convex/error_codes.json. Snapshot generation must remove deployment identity and produce stable ordering.
  • +
+

exit npx convex function-spec reports a non-null return schema for every client-callable function. The strict contract test finds zero v.any(), missing validators, unsupported validators, or unapproved public document IDs.

+

Phase 4: build the fail-closed generator

+
    +
  • Create the standalone Icarus tool under tool/icarus_convex_codegen/. Parse only the committed scrubbed spec during ordinary development.
  • +
  • Read convex/error_codes.json to emit the global ConvexErrorCode enum and its unknown case. Preserve an unknown raw server code on the exception.
  • +
  • Scan lib/collab/convex_payload_codecs.dart for @ConvexPayload codecs. Resolve each generic Dart type with the analyzer.
  • +
  • Generate the root factory, module interfaces, named parameters, ConvexQuery<T>, mutation and action methods, enums, unions, structural models, codecs, and the private implementation.
  • +
  • Recreate the owned output set under lib/collab/generated/ on every run. Format output. Run generation twice and require identical file paths and bytes.
  • +
  • Fail on missing or null validators, unknown nodes, v.any(), unbound opaque tags, unused or duplicate bindings, name collisions, public document IDs, warnings, or any public unknown type.
  • +
+
# Required developer workflow after the generator exists
+fvm dart run tool/icarus_convex_codegen/bin/generate.dart
+fvm dart format lib/collab/generated tool/icarus_convex_codegen
+git diff --exit-code -- convex/function_spec.json lib/collab/generated
+

exit Positive fixtures generate analyzable Dart. Every negative fixture fails for its intended reason. Renaming a function, argument, or result field breaks an unchanged typed caller at generation or analysis time.

+

Phase 5: normalize native and web transports

+
    +
  • Define one small private ConvexTransport interface and a closed normalized ConvexValue model.
  • +
  • Make query, mutation, action, and subscription results enter generated decoders through that model. Native JSON strings and JavaScript values may not reach a generated codec directly.
  • +
  • Preserve structured function error code, raw code, message, and data. Extend the vendored convex_flutter package if Phase 0 proves native data is missing.
  • +
  • Keep connection and authentication state on their existing streams. A recoverable function error does not close a subscription. A decoding or contract failure closes it.
  • +
  • Prove equivalent native and web values for null, omitted fields, numeric boundaries, bytes, nested objects, arrays, enums, errors, reconnects, and refreshed authentication.
  • +
+

exit The conformance suite sees identical normalized values and typed errors from both adapters. No production path parses an error message or calls jsonDecode on a generated result.

+

Phase 6: migrate repositories and collapse folder subscriptions

+
    +
  • Instantiate IcarusConvexApi with the platform transport. Keep generated imports inside the collaboration data layer.
  • +
  • Replace every string route and manual argument map in repositories, auth, migration, folder, strategy, share, media, and sync code with generated calls.
  • +
  • Map generated structural results to existing Icarus models inside named repository methods. Remove CloudFolderSummary, CloudStrategySummary, and other manual JSON DTOs only after their last caller moves.
  • +
  • Expose one shared repository folder-tree watch. Make Riverpod derive current children, roots, breadcrumbs, owned folders, and shared folders from that cached typed tree.
  • +
  • Keep strategy lists and active strategy snapshots separately scoped. Do not combine them with the folder tree.
  • +
+

exit Repository search finds no direct route strings outside transport or generated test fixtures. The cloud library holds one folder subscription and one current strategy-list subscription. Folder navigation, sharing, export, and editor sync tests pass.

+

Phase 7: lock drift out of CI and remove the old path

+
    +
  • Add an architecture test that rejects generated-client imports outside the allowed collaboration directory.
  • +
  • Add a CI job that deploys the current backend to an isolated Convex environment, captures and scrubs a fresh spec, and diffs it against convex/function_spec.json.
  • +
  • Run local generation from the committed spec and fail if it changes lib/collab/generated/.
  • +
  • Run the generated client tests and app tests on Windows CI. Add native build jobs for Windows and Linux so the wrapper cannot pass only on the macOS development machine.
  • +
  • Delete the legacy JSON decode helpers, generic StrategyOp envelope, stale DTOs, listForParent, and obsolete gauntlet-only assumptions. Keep the fair rerun artifacts as decision evidence.
  • +
  • Update docs/auth_flow_reference.md and any handoff that names deleted routes. Do not rewrite historical benchmark results.
  • +
+

exit A clean clone can regenerate the same API without a developer deployment. CI rejects a stale spec, stale output, public unknown type, direct route string, or forbidden import.

+
+
+

Test the contract from five directions

+
+ + + + + + + + + +
LayerWhat it must proveCompletion criterion
SchemaEvery public function has closed arguments, a closed return, allowed IDs, and a cataloged error path.The fresh isolated function spec matches the committed snapshot and every public function appears exactly once.
GeneratorEvery supported validator maps to strict Dart, and every unsupported shape stops generation.Positive, negative, golden, collision, mutation, stale-file, and deterministic fixtures all pass.
RuntimeNative and web transports deliver the same normalized values, errors, auth transitions, and subscription lifecycle.The same conformance corpus passes byte-for-byte normalized comparisons on both transports.
SyncProtocol version 3, revisions, immutable op IDs, persistence, replay, and user-visible status agree under faults.Seeded model tests and the 50,000-op gauntlet finish with no lost or duplicated work and no false synced state.
ProductA person can sign in, use the cloud library, edit on two independent clients, restart, and see the same data.Computer Use completes the release-build script below with timestamped visual receipts and zero manual reloads.
+
+

Source: ADR 0001, the corrected client gauntlet, cloud release gate, and current auth automation semantics.

+

Generator tests are a contract suite

+
    +
  • Unit-test every schema node the generator supports: null, boolean, integer, float, string, bytes, literal, optional, array, object, record, union, and recursive reference if the Convex spec can emit one.
  • +
  • Golden-test modules, escaped names, named parameters, enums, unions, structural results, query fetch() and watch(), mutations, actions, error enums, and exact payload bindings.
  • +
  • Negative-test missing and null validators, v.any(), unknown validator nodes, raw records at the public Dart boundary, unbound tags, unused bindings, duplicate bindings, route and type-name collisions, and unapproved public document IDs.
  • +
  • Mutation-test one function name, one argument name, one result field, one enum member, one payload tag, and one error code. Each mutation must either change generated output or fail generation. An unchanged typed caller must fail analysis after a breaking mutation.
  • +
  • Generate into a directory containing a stale owned file. The next run must delete it. Generate twice from shuffled but equivalent spec input. Both output trees must have the same paths and bytes.
  • +
  • Property-test codecs with fixed seeds. Every generated value that satisfies its validator must round-trip. Every value outside the validator must fail with the full endpoint and field path.
  • +
+

Sync tests cover every crash boundary

+
    +
  • Model every protocol request and result variant, including every rejected reason and unknown server error. Exhaustive switches may not use a default arm for known variants.
  • +
  • Crash after local persistence, after send but before response, after server apply but before receipt, while replacing queued intent, and while persisting a rejection. Restore from Hive after every crash point.
  • +
  • Exercise corrupt records, unsupported record versions, expired auth, rejected auth, reconnect, duplicate delivery, delayed delivery, out-of-order results, revision conflicts, delete and recreate, and subscription restart.
  • +
  • Run the deterministic gauntlet with 50 seeds and 1,000 operations per seed through two editing clients plus a fresh verifier. Keep the seed and trace for every failure.
  • +
  • Round-trip current and legacy .ica fixtures after the cloud cycle. Compare canonical data, not serialized key order. The local library before sign-in must remain byte-for-byte unchanged.
  • +
+
+
+

Final verification matrix

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
GateCommand or proofPass condition
TypeScriptnpx tsc --noEmitThe Convex source has zero type errors.
Protocolnpm run test:convexEvery version 3 request, result, rejection, auth, access-control, and revision case passes.
Authorizationconvex-test identities A, B, and C across every public function familyOwners, editors, viewers, unshared users, revoked users, and signed-out callers receive only their allowed data and operations.
Spec coverageFresh isolated npx convex function-spec plus strict auditEvery client-callable function has closed arguments and returns. The scrubbed snapshot has zero drift.
GeneratorGenerator unit, golden, negative, mutation, property, and collision testsPositive fixtures compile. Every unsupported or untyped fixture fails closed at the exact field path.
DeterminismGenerate twice from shuffled equivalent input, then git diff --exit-codeThe second run changes zero bytes and removes every stale owned file.
Breaking driftRename route, argument, result field, enum case, payload tag, and error code in fixturesEach mutation changes output, fails generation, or makes the unchanged typed caller fail analysis.
Dart typesfvm flutter analyze --no-fatal-infos plus generated-boundary auditNo analyzer error, dynamic, Object?, raw map, or untyped string enum crosses the wrapper.
CodecsFixed-seed valid and invalid value corpusValid values round-trip. Invalid values fail with endpoint and field path. Native and web bytes normalize identically.
Native bridgecargo test for changed Rust packages plus native conformance appStructured errors, auth refresh, reconnect, and subscription restart preserve their typed data.
Outbox modelFocused property and state-machine testsChanged intent always gets a new op ID. Equivalent retries keep one ID. Every state transition is legal.
Crash durabilityFive crash boundaries plus Hive restoreNo queued work disappears, no applied work is duplicated, and corrupt or unknown records fail visibly.
RepositoriesFocused auth, folder, strategy, share, media, migration, and sync integration testsEvery endpoint family uses generated calls and maps structural results to Icarus models.
Flutter behaviorfvm flutter testAll unit, widget, provider, semantics, and integration tests pass.
Round tripCurrent and legacy .ica import and export fixturesEvery fixture returns with no data loss. Signing in changes zero local-library bytes.
Web releasefvm flutter build web --no-wasm-dry-run --no-tree-shake-iconsThe release build succeeds through the web transport.
macOS releasefvm flutter build macosThe release app succeeds through convex_flutter.
Windows releaseWindows CI tests plus fvm flutter build windowsThe generated client, native package, full tests, and release build pass on Windows.
Linux releaseLinux CI tests plus fvm flutter build linuxThe generated client, native package, full tests, and release build pass on Linux.
Folder costCount live subscriptions during repeated navigationOne folder-tree subscription remains active. Navigation opens no parent subscription and performs no extra Convex call.
Gauntlet50 seeds, 1,000 operations per seed, two editors, one fresh verifierZero lost ops, duplicates, unresolved results, stale subscriptions, false synced states, or snapshot mismatches.
Performance10 paired before-and-after profile trials with alternating orderReport median and p95 convergence, reconnect, CPU, RSS, transfer, and bundle size. Investigate any median regression above 10% before merge.
Visible productComputer Use on macOS release plus web releaseThe full script below passes without manual reload, hidden errors, access-token leakage, or a false synced state.
+
+

Source: repository CI commands, the corrected client gauntlet, ADR 0001, and the implementation gates above.

+
# Final local gate on macOS after the new tool exists
+npx tsc --noEmit
+npm run test:convex
+(cd tool/icarus_convex_codegen && fvm dart pub get && fvm dart analyze && fvm dart test)
+fvm dart run tool/icarus_convex_codegen/bin/generate.dart
+fvm dart run tool/icarus_convex_codegen/bin/generate.dart
+git diff --exit-code -- convex/function_spec.json convex/error_codes.json lib/collab/generated
+fvm flutter analyze --no-fatal-infos
+fvm flutter test
+fvm flutter build web --no-wasm-dry-run --no-tree-shake-icons
+fvm flutter build macos
+cargo test --manifest-path third_party/convex_flutter/rust/Cargo.toml
+cargo test --manifest-path third_party/convex_rs/Cargo.toml
+git status --short
+

The final status may contain only the intended source, snapshot, generated output, tests, and documentation for this implementation.

+

Computer Use gate: prove the built product

+

This is a release-build acceptance run, not a demo against mocked providers. Use the final macOS app and the final web build against the same test deployment. The two clients may share the supplied account, but they must not share local storage.

+
    +
  1. Record the commit SHA, Convex deployment, Supabase project, app version, UTC start time, macOS version, and browser version. Create a unique run name such as typed-wrapper-e2e-20260829T143000Z.
  2. +
  3. Launch build/macos/Build/Products/Release/icarus.app through Computer Use. Start signed out. Count the local library and export one known local strategy. Keep that export for the final byte comparison.
  4. +
  5. Open the login dialog. Enter the email and password from the test-auth block, then submit. Decline any password-save prompt.
  6. +
  7. After the dialog closes, capture the first screenshot. Confirm Cloud becomes available and the signed-in account indicator appears. Confirm the local library count and known strategy remain unchanged.
  8. +
  9. In Cloud, create a top-level folder named with the run name, create one child folder, and create a strategy inside it. Add two pages, an element, and a lineup. Wait for the visible sync chip to say Synced.
  10. +
  11. Serve the release web build and open it in a clean browser profile. Sign in with the same test credentials. Without reloading, confirm the folder tree, child folder, strategy, pages, element, and lineup appear.
  12. +
  13. Rename the child folder in web. Move the strategy between the two run folders. Patch the element and lineup positions. Confirm macOS receives each change without a reload and keeps the correct breadcrumb.
  14. +
  15. Edit the same strategy from both clients. Exercise an accepted edit and an intentional stale-revision edit. The accepted edit must converge. The rejected edit must produce the documented visible state and must never flash Synced.
  16. +
  17. Close the macOS app after it reaches Synced. Reopen it through Computer Use and confirm the complete cloud state. Sign out, sign in again, and confirm the same state one more time.
  18. +
  19. Export the cloud strategy, import it into local mode under a new name, and compare its canonical content with the cloud copy. Compare the original local export from step 2 with a fresh export. Both comparisons must lose zero supported data.
  20. +
  21. Review the accessibility tree after every navigation or asynchronous state change. Use fresh element indices. If a required control is missing, add a stable semantic label and widget test, rebuild, and restart this run.
  22. +
  23. Capture screenshots after sign-in, initial sync, cross-client rename and move, accepted edit, rejected edit, restart readback, and round trip. Record timestamps so the receipts can be matched to logs and server events.
  24. +
  25. After evidence is saved, delete only the cloud folder whose exact name starts with this run name. Keep the original local library untouched. Record cleanup success in the receipt.
  26. +
+

The supplied credential proves same-account sync across independent clients. It does not prove cross-account sharing or revocation. The automated authorization matrix remains mandatory unless Dara supplies separate live accounts B and C. Do not create accounts to make the live script look complete.

+

red loop Pause the run on data loss, an access-control leak, a reconnect loop, a stale subscription, an unreadable record, or any moment where Synced is shown while work is still local, rejected, failed, or uncertain. Preserve the run data and logs, fix the root cause, add a regression test, rebuild, and restart at the earliest affected step. Then finish the script.

+

Source: cloud release gate, auth automation semantics, current sync-status keys, and the Computer Use workflow.

+
+
+

Commit boundaries and stop conditions

+
+ + + + + + + + + + + +
CommitContainsMust be green before push
1Immutable op-ID repair and regression testsFocused outbox tests and Flutter analysis
2Protocol version 3, development wipe path, typed outcomesConvex protocol tests and Dart sync tests
3Public validators, internalized jobs, scrubbed specConvex tests and strict spec audit
4Generator, bindings, generated API, contract fixturesGenerator tests, deterministic regeneration, analysis
5Normalized transports and any required fork extensionNative and web conformance suite
6Repository migration, one folder tree, DTO removalFlutter tests, web build, macOS build
7CI drift gates, architecture test, reference cleanupFull final verification matrix
+
+

Source: dependency order in ADR 0001. Each commit is reviewable and leaves the branch buildable.

+

Use one draft PR with reviewable phase commits

+

Use one pull request, not a stack. The protocol, function spec, generator, transports, and repository migration describe one contract. A stacked review would make each earlier PR expose a temporary shape, repeat generated-code review, and force every later branch to rebase after a contract fix.

+
    +
  1. Open one draft PR after the first green phase commit so Windows, Linux, and macOS CI can run throughout implementation. Keep the seven phase commits separate and buildable.
  2. +
  3. Push after each phase. Treat CI failures with the same evidence, fix, regression, rerun loop. Do not request final review while a phase or platform is red.
  4. +
  5. Write the PR description as a map: accepted architecture, phase commits, generated files, protocol change, test matrix, Computer Use receipts, performance comparison, and known non-goals.
  6. +
  7. After all seven phases and the full final matrix pass, mark the PR ready and trigger Greptile. Old draft reviews do not count unless they apply to the current head SHA.
  8. +
  9. For each actionable Greptile finding, verify it against current source, fix it, add or strengthen a regression test, run focused and broad gates, commit, push, and request a fresh review with @greptileai.
  10. +
  11. Poll the current SHA until every required check is green, Greptile reports 5/5 or no actionable findings, and a thread-aware read finds no unresolved actionable review thread. A summary comment alone is not proof.
  12. +
  13. Finish with a clean local branch whose SHA matches the PR head. Report the PR URL, final SHA, commits added during review, every verification result, and any skipped gate with its exact blocker.
  14. +
+

decision One large diff is the cost. Phase commits, a file map, and receipts make it reviewable without paying the larger coordination cost of a PR stack.

+

Use the repair loop for ordinary failures. Ask Dara only when one of these blocks progress:

+
    +
  • The only path forward would weaken an accepted contract or change a user-selected design decision.
  • +
  • The fix would change the local Hive schema, require a local migration, or change the .ica format. Those are outside this plan.
  • +
  • A required deployment permission, signing identity, or supported-platform runner is unavailable after safe in-scope alternatives are exhausted.
  • +
  • Two current requirements conflict and the repository does not contain evidence that resolves the conflict.
  • +
+
+
+

Source inventory

+
+ Current seams the implementation must retire or preserve +
# Current manual boundary
+lib/collab/convex_strategy_repository.dart
+lib/collab/collab_models.dart
+lib/collab/convex_client.dart
+lib/collab/src/convex_client_native.dart
+lib/collab/src/convex_client_web.dart
+# Durable sync and replay
+lib/collab/durable_strategy_outbox.dart
+lib/providers/collab/strategy_op_queue_provider.dart
+lib/providers/collab/active_page_live_sync_models.dart
+convex/ops.ts
+convex/lib/opTypes.ts
+# Folder subscription collapse
+convex/folders.ts
+lib/providers/collab/remote_library_provider.dart
+lib/widgets/folder_content.dart
+lib/widgets/folder_navigator_sidebar.dart
+lib/widgets/current_path_bar.dart
+# Contract and runtime evidence
+tool/convex_client_gauntlet/
+docs/cloud_sync_refactor/convex_dart_client_fair_rerun_result.md
+

Repository audit at be63971 on 2026-08-29.

+
+
+ Decision evidence and reusable fixtures + +
+
+

Generated 2026-08-29 · Codex with Dara's accepted design decisions · repository base be63971 · verification expansion based on 2191788 · source audit on macOS · revision v3 · implementation not started

+
+
+
+ + + diff --git a/lib/collab/cloud_library_models.dart b/lib/collab/cloud_library_models.dart new file mode 100644 index 00000000..57dbe995 --- /dev/null +++ b/lib/collab/cloud_library_models.dart @@ -0,0 +1,11 @@ +import 'package:icarus/domain/folder.dart'; +import 'package:icarus/strategy/strategy_models.dart'; + +typedef CloudFolderEntry = ({Folder folder, String role}); + +typedef CloudStrategyEntry = ({ + StrategyData strategy, + int revision, + String role, + String attackLabel, +}); diff --git a/lib/collab/cloud_media_models.dart b/lib/collab/cloud_media_models.dart index d069be3f..43808a07 100644 --- a/lib/collab/cloud_media_models.dart +++ b/lib/collab/cloud_media_models.dart @@ -152,23 +152,6 @@ class CloudImageUploadIntent { final Map requiredHeaders; final DateTime expiresAt; final int maxBytes; - - factory CloudImageUploadIntent.fromJson(Map json) { - final headers = json['requiredHeaders']; - return CloudImageUploadIntent( - provider: json['provider'] as String? ?? 'r2', - uploadId: json['uploadId'] as String, - objectKey: json['objectKey'] as String, - uploadUrl: json['uploadUrl'] as String, - requiredHeaders: headers is Map - ? headers.map((key, value) => MapEntry('$key', '$value')) - : const {}, - expiresAt: DateTime.fromMillisecondsSinceEpoch( - (json['expiresAt'] as num).toInt(), - ), - maxBytes: (json['maxBytes'] as num?)?.toInt() ?? 0, - ); - } } Map cloudImagePayloadFromPlacedImage(PlacedImage image) { diff --git a/lib/collab/collab_models.dart b/lib/collab/collab_models.dart index fadd8c7a..844466e3 100644 --- a/lib/collab/collab_models.dart +++ b/lib/collab/collab_models.dart @@ -1,10 +1,6 @@ import 'dart:convert'; -enum StrategyOpKind { add, move, patch, delete, reorder } - -enum StrategyOpEntityType { strategy, page, pageContent, element, lineup } - -const currentCloudProtocolVersion = 2; +const currentCloudProtocolVersion = 3; const currentCloudPayloadVersion = 1; typedef CloudPayload = Map; @@ -81,68 +77,857 @@ CloudPayload cloudObjectPayload(Object? payload) { return cloudObjectPayloadOrNull(payload) ?? {}; } -class StrategyOp { - const StrategyOp({ +enum StrategyOpType { + strategyPatch('strategy.patch'), + pageAdd('page.add'), + pagePatch('page.patch'), + pageDelete('page.delete'), + pageReorder('page.reorder'), + pageContentPatch('pageContent.patch'), + elementAdd('element.add'), + elementPatch('element.patch'), + elementDelete('element.delete'), + elementReorder('element.reorder'), + lineupAdd('lineup.add'), + lineupPatch('lineup.patch'), + lineupDelete('lineup.delete'), + lineupReorder('lineup.reorder'); + + const StrategyOpType(this.wireName); + + final String wireName; + + static StrategyOpType fromWireName(String value) => + values.firstWhere((type) => type.wireName == value); +} + +enum StrategyOpKind { add, patch, delete, reorder } + +enum StrategyOpEntityType { strategy, page, pageContent, element, lineup } + +sealed class StrategyOp { + const StrategyOp(); + + String get opId; + StrategyOpType get type; + StrategyOpKind get kind => switch (type) { + StrategyOpType.strategyPatch || + StrategyOpType.pagePatch || + StrategyOpType.pageContentPatch || + StrategyOpType.elementPatch || + StrategyOpType.lineupPatch => + StrategyOpKind.patch, + StrategyOpType.pageAdd || + StrategyOpType.elementAdd || + StrategyOpType.lineupAdd => + StrategyOpKind.add, + StrategyOpType.pageDelete || + StrategyOpType.elementDelete || + StrategyOpType.lineupDelete => + StrategyOpKind.delete, + StrategyOpType.pageReorder || + StrategyOpType.elementReorder || + StrategyOpType.lineupReorder => + StrategyOpKind.reorder, + }; + StrategyOpEntityType get entityType => switch (type) { + StrategyOpType.strategyPatch => StrategyOpEntityType.strategy, + StrategyOpType.pageAdd || + StrategyOpType.pagePatch || + StrategyOpType.pageDelete || + StrategyOpType.pageReorder => + StrategyOpEntityType.page, + StrategyOpType.pageContentPatch => StrategyOpEntityType.pageContent, + StrategyOpType.elementAdd || + StrategyOpType.elementPatch || + StrategyOpType.elementDelete || + StrategyOpType.elementReorder => + StrategyOpEntityType.element, + StrategyOpType.lineupAdd || + StrategyOpType.lineupPatch || + StrategyOpType.lineupDelete || + StrategyOpType.lineupReorder => + StrategyOpEntityType.lineup, + }; + + String? get entityPublicId => switch (this) { + StrategyPatchOp() => null, + PageAddOp(:final pagePublicId) || + PagePatchOp(:final pagePublicId) || + PageDeleteOp(:final pagePublicId) || + PageReorderOp(:final pagePublicId) || + PageContentPatchOp(:final pagePublicId) => + pagePublicId, + ElementAddOp(:final elementPublicId) || + ElementPatchOp(:final elementPublicId) || + ElementDeleteOp(:final elementPublicId) || + ElementReorderOp(:final elementPublicId) => + elementPublicId, + LineupAddOp(:final lineupPublicId) || + LineupPatchOp(:final lineupPublicId) || + LineupDeleteOp(:final lineupPublicId) || + LineupReorderOp(:final lineupPublicId) => + lineupPublicId, + }; + + String? get pagePublicId => switch (this) { + PageAddOp(:final pagePublicId) || + PagePatchOp(:final pagePublicId) || + PageDeleteOp(:final pagePublicId) || + PageReorderOp(:final pagePublicId) || + PageContentPatchOp(:final pagePublicId) || + ElementAddOp(:final pagePublicId) || + LineupAddOp(:final pagePublicId) => + pagePublicId, + ElementPatchOp(:final pagePublicId) || + LineupPatchOp(:final pagePublicId) => + pagePublicId, + ElementDeleteOp(:final pagePublicId) || + ElementReorderOp(:final pagePublicId) || + LineupDeleteOp(:final pagePublicId) || + LineupReorderOp(:final pagePublicId) => + pagePublicId, + StrategyPatchOp() => null, + }; + + Object? get payload => switch (this) { + StrategyPatchOp(:final payload) || + PageAddOp(:final payload) || + PagePatchOp(:final payload) || + ElementAddOp(:final payload) || + LineupAddOp(:final payload) => + payload, + ElementPatchOp(:final payload) || + LineupPatchOp(:final payload) => + payload, + PageContentPatchOp(:final settings) => {'settings': settings}, + PageDeleteOp() || + PageReorderOp() || + ElementDeleteOp() || + ElementReorderOp() || + LineupDeleteOp() || + LineupReorderOp() => + null, + }; + + int? get sortIndex => switch (this) { + PageAddOp(:final sortIndex) || + PageReorderOp(:final sortIndex) || + ElementAddOp(:final sortIndex) || + ElementReorderOp(:final sortIndex) || + LineupAddOp(:final sortIndex) || + LineupReorderOp(:final sortIndex) => + sortIndex, + ElementPatchOp(:final sortIndex) || + LineupPatchOp(:final sortIndex) => + sortIndex, + StrategyPatchOp() || + PagePatchOp() || + PageDeleteOp() || + PageContentPatchOp() || + ElementDeleteOp() || + LineupDeleteOp() => + null, + }; + + int? get expectedRevision => switch (this) { + StrategyPatchOp(:final expectedStrategyRevision) || + PageAddOp(:final expectedStrategyRevision) || + PageDeleteOp(:final expectedStrategyRevision) || + PageReorderOp(:final expectedStrategyRevision) => + expectedStrategyRevision, + PagePatchOp(:final expectedPageRevision) => expectedPageRevision, + PageContentPatchOp(:final expectedPageContentRevision) => + expectedPageContentRevision, + ElementAddOp(:final expectedElementRevision) => expectedElementRevision, + ElementPatchOp(:final expectedElementRevision) || + ElementDeleteOp(:final expectedElementRevision) || + ElementReorderOp(:final expectedElementRevision) => + expectedElementRevision, + LineupAddOp(:final expectedLineupRevision) => expectedLineupRevision, + LineupPatchOp(:final expectedLineupRevision) || + LineupDeleteOp(:final expectedLineupRevision) || + LineupReorderOp(:final expectedLineupRevision) => + expectedLineupRevision, + }; + + Map toConvexJson() => switch (this) { + StrategyPatchOp() => { + 'opId': opId, + 'type': type.wireName, + 'payload': payload, + 'expectedStrategyRevision': expectedRevision, + }, + PageAddOp() => { + 'opId': opId, + 'type': type.wireName, + 'pagePublicId': pagePublicId, + 'payload': payload, + 'sortIndex': sortIndex, + 'expectedStrategyRevision': expectedRevision, + }, + PagePatchOp() => { + 'opId': opId, + 'type': type.wireName, + 'pagePublicId': pagePublicId, + 'payload': payload, + 'expectedPageRevision': expectedRevision, + }, + PageDeleteOp() => { + 'opId': opId, + 'type': type.wireName, + 'pagePublicId': pagePublicId, + 'expectedStrategyRevision': expectedRevision, + }, + PageReorderOp() => { + 'opId': opId, + 'type': type.wireName, + 'pagePublicId': pagePublicId, + 'sortIndex': sortIndex, + 'expectedStrategyRevision': expectedRevision, + }, + PageContentPatchOp(:final settings) => { + 'opId': opId, + 'type': type.wireName, + 'pagePublicId': pagePublicId, + 'settings': settings, + 'expectedPageContentRevision': expectedRevision, + }, + ElementAddOp() => { + 'opId': opId, + 'type': type.wireName, + 'elementPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'payload': payload, + 'sortIndex': sortIndex, + if (expectedRevision != null) + 'expectedElementRevision': expectedRevision, + }, + ElementPatchOp() => { + 'opId': opId, + 'type': type.wireName, + 'elementPublicId': entityPublicId, + if (pagePublicId != null) 'pagePublicId': pagePublicId, + if (payload != null) 'payload': payload, + if (sortIndex != null) 'sortIndex': sortIndex, + 'expectedElementRevision': expectedRevision, + }, + ElementDeleteOp() => { + 'opId': opId, + 'type': type.wireName, + 'elementPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'expectedElementRevision': expectedRevision, + }, + ElementReorderOp() => { + 'opId': opId, + 'type': type.wireName, + 'elementPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'sortIndex': sortIndex, + 'expectedElementRevision': expectedRevision, + }, + LineupAddOp() => { + 'opId': opId, + 'type': type.wireName, + 'lineupPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'payload': payload, + 'sortIndex': sortIndex, + if (expectedRevision != null) + 'expectedLineupRevision': expectedRevision, + }, + LineupPatchOp() => { + 'opId': opId, + 'type': type.wireName, + 'lineupPublicId': entityPublicId, + if (pagePublicId != null) 'pagePublicId': pagePublicId, + if (payload != null) 'payload': payload, + if (sortIndex != null) 'sortIndex': sortIndex, + 'expectedLineupRevision': expectedRevision, + }, + LineupDeleteOp() => { + 'opId': opId, + 'type': type.wireName, + 'lineupPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'expectedLineupRevision': expectedRevision, + }, + LineupReorderOp() => { + 'opId': opId, + 'type': type.wireName, + 'lineupPublicId': entityPublicId, + 'pagePublicId': pagePublicId, + 'sortIndex': sortIndex, + 'expectedLineupRevision': expectedRevision, + }, + }; + + factory StrategyOp.fromJson(Map json) { + final opId = json['opId'] as String; + return switch (StrategyOpType.fromWireName(json['type'] as String)) { + StrategyOpType.strategyPatch => StrategyPatchOp( + opId: opId, + payload: _requiredMap(json['payload'], 'payload'), + expectedStrategyRevision: + _requiredInt(json['expectedStrategyRevision']), + ), + StrategyOpType.pageAdd => PageAddOp( + opId: opId, + pagePublicId: json['pagePublicId'] as String, + payload: _requiredMap(json['payload'], 'payload'), + sortIndex: _requiredInt(json['sortIndex']), + expectedStrategyRevision: + _requiredInt(json['expectedStrategyRevision']), + ), + StrategyOpType.pagePatch => PagePatchOp( + opId: opId, + pagePublicId: json['pagePublicId'] as String, + payload: _requiredMap(json['payload'], 'payload'), + expectedPageRevision: _requiredInt(json['expectedPageRevision']), + ), + StrategyOpType.pageDelete => PageDeleteOp( + opId: opId, + pagePublicId: json['pagePublicId'] as String, + expectedStrategyRevision: + _requiredInt(json['expectedStrategyRevision']), + ), + StrategyOpType.pageReorder => PageReorderOp( + opId: opId, + pagePublicId: json['pagePublicId'] as String, + sortIndex: _requiredInt(json['sortIndex']), + expectedStrategyRevision: + _requiredInt(json['expectedStrategyRevision']), + ), + StrategyOpType.pageContentPatch => PageContentPatchOp( + opId: opId, + pagePublicId: json['pagePublicId'] as String, + settings: _requiredMap(json['settings'], 'settings'), + expectedPageContentRevision: + _requiredInt(json['expectedPageContentRevision']), + ), + StrategyOpType.elementAdd => ElementAddOp( + opId: opId, + elementPublicId: json['elementPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + payload: _requiredMap(json['payload'], 'payload'), + sortIndex: _requiredInt(json['sortIndex']), + expectedElementRevision: + (json['expectedElementRevision'] as num?)?.toInt(), + ), + StrategyOpType.elementPatch => ElementPatchOp( + opId: opId, + elementPublicId: json['elementPublicId'] as String, + pagePublicId: json['pagePublicId'] as String?, + payload: _optionalMap(json['payload']), + sortIndex: (json['sortIndex'] as num?)?.toInt(), + expectedElementRevision: + _requiredInt(json['expectedElementRevision']), + ), + StrategyOpType.elementDelete => ElementDeleteOp( + opId: opId, + elementPublicId: json['elementPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + expectedElementRevision: + _requiredInt(json['expectedElementRevision']), + ), + StrategyOpType.elementReorder => ElementReorderOp( + opId: opId, + elementPublicId: json['elementPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + sortIndex: _requiredInt(json['sortIndex']), + expectedElementRevision: + _requiredInt(json['expectedElementRevision']), + ), + StrategyOpType.lineupAdd => LineupAddOp( + opId: opId, + lineupPublicId: json['lineupPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + payload: _requiredMap(json['payload'], 'payload'), + sortIndex: _requiredInt(json['sortIndex']), + expectedLineupRevision: + (json['expectedLineupRevision'] as num?)?.toInt(), + ), + StrategyOpType.lineupPatch => LineupPatchOp( + opId: opId, + lineupPublicId: json['lineupPublicId'] as String, + pagePublicId: json['pagePublicId'] as String?, + payload: _optionalMap(json['payload']), + sortIndex: (json['sortIndex'] as num?)?.toInt(), + expectedLineupRevision: _requiredInt(json['expectedLineupRevision']), + ), + StrategyOpType.lineupDelete => LineupDeleteOp( + opId: opId, + lineupPublicId: json['lineupPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + expectedLineupRevision: _requiredInt(json['expectedLineupRevision']), + ), + StrategyOpType.lineupReorder => LineupReorderOp( + opId: opId, + lineupPublicId: json['lineupPublicId'] as String, + pagePublicId: json['pagePublicId'] as String, + sortIndex: _requiredInt(json['sortIndex']), + expectedLineupRevision: _requiredInt(json['expectedLineupRevision']), + ), + }; + } + + StrategyOp withOpId(String value) => switch (this) { + StrategyPatchOp(:final payload, :final expectedStrategyRevision) => + StrategyPatchOp( + opId: value, + payload: payload, + expectedStrategyRevision: expectedStrategyRevision, + ), + PageAddOp( + :final pagePublicId, + :final payload, + :final sortIndex, + :final expectedStrategyRevision, + ) => + PageAddOp( + opId: value, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedStrategyRevision: expectedStrategyRevision, + ), + PagePatchOp( + :final pagePublicId, + :final payload, + :final expectedPageRevision, + ) => + PagePatchOp( + opId: value, + pagePublicId: pagePublicId, + payload: payload, + expectedPageRevision: expectedPageRevision, + ), + PageDeleteOp(:final pagePublicId, :final expectedStrategyRevision) => + PageDeleteOp( + opId: value, + pagePublicId: pagePublicId, + expectedStrategyRevision: expectedStrategyRevision, + ), + PageReorderOp( + :final pagePublicId, + :final sortIndex, + :final expectedStrategyRevision, + ) => + PageReorderOp( + opId: value, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedStrategyRevision: expectedStrategyRevision, + ), + PageContentPatchOp( + :final pagePublicId, + :final settings, + :final expectedPageContentRevision, + ) => + PageContentPatchOp( + opId: value, + pagePublicId: pagePublicId, + settings: settings, + expectedPageContentRevision: expectedPageContentRevision, + ), + ElementAddOp( + :final elementPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + :final expectedElementRevision, + ) => + ElementAddOp( + opId: value, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: expectedElementRevision, + ), + ElementPatchOp( + :final elementPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + :final expectedElementRevision, + ) => + ElementPatchOp( + opId: value, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: expectedElementRevision, + ), + ElementDeleteOp( + :final elementPublicId, + :final pagePublicId, + :final expectedElementRevision, + ) => + ElementDeleteOp( + opId: value, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + expectedElementRevision: expectedElementRevision, + ), + ElementReorderOp( + :final elementPublicId, + :final pagePublicId, + :final sortIndex, + :final expectedElementRevision, + ) => + ElementReorderOp( + opId: value, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedElementRevision: expectedElementRevision, + ), + LineupAddOp( + :final lineupPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + :final expectedLineupRevision, + ) => + LineupAddOp( + opId: value, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: expectedLineupRevision, + ), + LineupPatchOp( + :final lineupPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + :final expectedLineupRevision, + ) => + LineupPatchOp( + opId: value, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: expectedLineupRevision, + ), + LineupDeleteOp( + :final lineupPublicId, + :final pagePublicId, + :final expectedLineupRevision, + ) => + LineupDeleteOp( + opId: value, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + expectedLineupRevision: expectedLineupRevision, + ), + LineupReorderOp( + :final lineupPublicId, + :final pagePublicId, + :final sortIndex, + :final expectedLineupRevision, + ) => + LineupReorderOp( + opId: value, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedLineupRevision: expectedLineupRevision, + ), + }; +} + +final class StrategyPatchOp extends StrategyOp { + const StrategyPatchOp({ + required this.opId, + required this.payload, + required this.expectedStrategyRevision, + }); + @override + final String opId; + @override + final CloudPayload payload; + final int expectedStrategyRevision; + @override + StrategyOpType get type => StrategyOpType.strategyPatch; +} + +final class PageAddOp extends StrategyOp { + const PageAddOp({ + required this.opId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + required this.expectedStrategyRevision, + }); + @override + final String opId; + @override + final String pagePublicId; + @override + final CloudPayload payload; + @override + final int sortIndex; + final int expectedStrategyRevision; + @override + StrategyOpType get type => StrategyOpType.pageAdd; +} + +final class PagePatchOp extends StrategyOp { + const PagePatchOp({ + required this.opId, + required this.pagePublicId, + required this.payload, + required this.expectedPageRevision, + }); + @override + final String opId; + @override + final String pagePublicId; + @override + final CloudPayload payload; + final int expectedPageRevision; + @override + StrategyOpType get type => StrategyOpType.pagePatch; +} + +final class PageDeleteOp extends StrategyOp { + const PageDeleteOp({ + required this.opId, + required this.pagePublicId, + required this.expectedStrategyRevision, + }); + @override + final String opId; + @override + final String pagePublicId; + final int expectedStrategyRevision; + @override + StrategyOpType get type => StrategyOpType.pageDelete; +} + +final class PageReorderOp extends StrategyOp { + const PageReorderOp({ + required this.opId, + required this.pagePublicId, + required this.sortIndex, + required this.expectedStrategyRevision, + }); + @override + final String opId; + @override + final String pagePublicId; + @override + final int sortIndex; + final int expectedStrategyRevision; + @override + StrategyOpType get type => StrategyOpType.pageReorder; +} + +final class PageContentPatchOp extends StrategyOp { + const PageContentPatchOp({ + required this.opId, + required this.pagePublicId, + required this.settings, + required this.expectedPageContentRevision, + }); + @override + final String opId; + @override + final String pagePublicId; + final CloudPayload settings; + final int expectedPageContentRevision; + @override + StrategyOpType get type => StrategyOpType.pageContentPatch; +} + +final class ElementAddOp extends StrategyOp { + const ElementAddOp({ + required this.opId, + required this.elementPublicId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + this.expectedElementRevision, + }); + @override + final String opId; + final String elementPublicId; + @override + final String pagePublicId; + @override + final CloudPayload payload; + @override + final int sortIndex; + final int? expectedElementRevision; + @override + StrategyOpType get type => StrategyOpType.elementAdd; +} + +final class ElementPatchOp extends StrategyOp { + const ElementPatchOp({ required this.opId, - required this.kind, - required this.entityType, - this.entityPublicId, + required this.elementPublicId, + required this.expectedElementRevision, this.pagePublicId, this.payload, this.sortIndex, - this.expectedRevision, }); + @override + final String opId; + final String elementPublicId; + @override + final String? pagePublicId; + @override + final CloudPayload? payload; + @override + final int? sortIndex; + final int expectedElementRevision; + @override + StrategyOpType get type => StrategyOpType.elementPatch; +} + +final class ElementDeleteOp extends StrategyOp { + const ElementDeleteOp({ + required this.opId, + required this.elementPublicId, + required this.pagePublicId, + required this.expectedElementRevision, + }); + @override + final String opId; + final String elementPublicId; + @override + final String pagePublicId; + final int expectedElementRevision; + @override + StrategyOpType get type => StrategyOpType.elementDelete; +} + +final class ElementReorderOp extends StrategyOp { + const ElementReorderOp({ + required this.opId, + required this.elementPublicId, + required this.pagePublicId, + required this.sortIndex, + required this.expectedElementRevision, + }); + @override + final String opId; + final String elementPublicId; + @override + final String pagePublicId; + @override + final int sortIndex; + final int expectedElementRevision; + @override + StrategyOpType get type => StrategyOpType.elementReorder; +} + +final class LineupAddOp extends StrategyOp { + const LineupAddOp({ + required this.opId, + required this.lineupPublicId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + this.expectedLineupRevision, + }); + @override + final String opId; + final String lineupPublicId; + @override + final String pagePublicId; + @override + final CloudPayload payload; + @override + final int sortIndex; + final int? expectedLineupRevision; + @override + StrategyOpType get type => StrategyOpType.lineupAdd; +} +final class LineupPatchOp extends StrategyOp { + const LineupPatchOp({ + required this.opId, + required this.lineupPublicId, + required this.expectedLineupRevision, + this.pagePublicId, + this.payload, + this.sortIndex, + }); + @override final String opId; - final StrategyOpKind kind; - final StrategyOpEntityType entityType; - final String? entityPublicId; + final String lineupPublicId; + @override final String? pagePublicId; - final Object? payload; + @override + final CloudPayload? payload; + @override final int? sortIndex; - final int? expectedRevision; - - Map toConvexJson() { - return { - 'opId': opId, - 'kind': kind.name, - 'entityType': entityType.name, - if (entityPublicId != null) 'entityPublicId': entityPublicId, - if (pagePublicId != null) 'pagePublicId': pagePublicId, - if (payload != null) 'payload': payload, - if (sortIndex != null) 'sortIndex': sortIndex, - if (expectedRevision != null) 'expectedRevision': expectedRevision, - }; - } + final int expectedLineupRevision; + @override + StrategyOpType get type => StrategyOpType.lineupPatch; +} - factory StrategyOp.fromJson(Map json) { - return StrategyOp( - opId: json['opId'] as String, - kind: StrategyOpKind.values.byName(json['kind'] as String), - entityType: - StrategyOpEntityType.values.byName(json['entityType'] as String), - entityPublicId: json['entityPublicId'] as String?, - pagePublicId: json['pagePublicId'] as String?, - payload: json['payload'], - sortIndex: (json['sortIndex'] as num?)?.toInt(), - expectedRevision: (json['expectedRevision'] as num?)?.toInt(), - ); - } +final class LineupDeleteOp extends StrategyOp { + const LineupDeleteOp({ + required this.opId, + required this.lineupPublicId, + required this.pagePublicId, + required this.expectedLineupRevision, + }); + @override + final String opId; + final String lineupPublicId; + @override + final String pagePublicId; + final int expectedLineupRevision; + @override + StrategyOpType get type => StrategyOpType.lineupDelete; +} - StrategyOp copyWith({ - int? expectedRevision, - }) { - return StrategyOp( - opId: opId, - kind: kind, - entityType: entityType, - entityPublicId: entityPublicId, - pagePublicId: pagePublicId, - payload: payload, - sortIndex: sortIndex, - expectedRevision: expectedRevision ?? this.expectedRevision, - ); - } +final class LineupReorderOp extends StrategyOp { + const LineupReorderOp({ + required this.opId, + required this.lineupPublicId, + required this.pagePublicId, + required this.sortIndex, + required this.expectedLineupRevision, + }); + @override + final String opId; + final String lineupPublicId; + @override + final String pagePublicId; + @override + final int sortIndex; + final int expectedLineupRevision; + @override + StrategyOpType get type => StrategyOpType.lineupReorder; +} + +Map _requiredMap(Object? value, String field) { + final map = _optionalMap(value); + if (map == null) throw FormatException('Op $field must be an object'); + return map; +} + +Map? _optionalMap(Object? value) { + if (value == null) return null; + if (value is Map) return value; + if (value is Map) return Map.from(value); + throw const FormatException('Op value must be an object'); +} + +int _requiredInt(Object? value) { + if (value is num) return value.toInt(); + throw const FormatException('Op revision or index must be a number'); } class PendingOp { @@ -168,39 +953,171 @@ class PendingOp { } } -class OpAck { - const OpAck({ - required this.opId, - required this.status, - this.reason, - this.appliedRevision, - this.latestRevision, - this.latestPayload, +enum OpRejectionReason { + alreadyExists('already_exists'), + elementStrategyMismatch('element_strategy_mismatch'), + lineupStrategyMismatch('lineup_strategy_mismatch'), + missingExpectedRevision('missing_expected_revision'), + notFound('not_found'), + pageStrategyMismatch('page_strategy_mismatch'), + revisionMismatch('revision_mismatch'); + + const OpRejectionReason(this.wireName); + final String wireName; + + static OpRejectionReason fromWireName(String value) => + values.firstWhere((reason) => reason.wireName == value); +} + +sealed class CurrentOpSnapshot { + const CurrentOpSnapshot({required this.revision, required this.value}); + final int revision; + final CloudPayload value; + + factory CurrentOpSnapshot.fromJson(Map json) { + final revision = _requiredInt(json['revision']); + final value = _requiredMap(json['value'], 'current.value'); + return switch (json['type']) { + 'strategy' => StrategyCurrentSnapshot(revision: revision, value: value), + 'page' => PageCurrentSnapshot(revision: revision, value: value), + 'pageContent' => + PageContentCurrentSnapshot(revision: revision, value: value), + 'element' => ElementCurrentSnapshot(revision: revision, value: value), + 'lineup' => LineupCurrentSnapshot(revision: revision, value: value), + final Object? type => throw FormatException( + 'Unknown current op snapshot type: $type', + ), + }; + } +} + +final class StrategyCurrentSnapshot extends CurrentOpSnapshot { + const StrategyCurrentSnapshot( + {required super.revision, required super.value}); +} + +final class PageCurrentSnapshot extends CurrentOpSnapshot { + const PageCurrentSnapshot({required super.revision, required super.value}); +} + +final class PageContentCurrentSnapshot extends CurrentOpSnapshot { + const PageContentCurrentSnapshot({ + required super.revision, + required super.value, }); +} - final String opId; - final String status; - final String? reason; - final int? appliedRevision; - final int? latestRevision; - final CloudPayload? latestPayload; +final class ElementCurrentSnapshot extends CurrentOpSnapshot { + const ElementCurrentSnapshot({required super.revision, required super.value}); +} - bool get isAck => status == 'ack'; +final class LineupCurrentSnapshot extends CurrentOpSnapshot { + const LineupCurrentSnapshot({required super.revision, required super.value}); +} + +sealed class OpAck { + const OpAck(); + + String get opId; + String get status => switch (this) { + AppliedOpAck() => 'applied', + NoopOpAck() => 'noop', + RejectedOpAck() => 'rejected', + FailedOpAck() => 'failed', + }; + bool get isAck => this is AppliedOpAck || this is NoopOpAck; + String? get reason => switch (this) { + RejectedOpAck(:final rejectionReason) => rejectionReason.wireName, + FailedOpAck(:final message) => message, + AppliedOpAck() || NoopOpAck() => null, + }; + int? get appliedRevision => switch (this) { + AppliedOpAck(:final revision) => revision, + NoopOpAck(:final currentRevision) => currentRevision, + RejectedOpAck() || FailedOpAck() => null, + }; + int? get latestRevision => switch (this) { + RejectedOpAck(:final current) => current?.revision, + AppliedOpAck() || NoopOpAck() || FailedOpAck() => null, + }; + CloudPayload? get latestPayload => switch (this) { + RejectedOpAck(:final current) => current?.value, + AppliedOpAck() || NoopOpAck() || FailedOpAck() => null, + }; factory OpAck.fromJson(Map json) { - return OpAck( - opId: json['opId'] as String, - status: json['status'] as String, - reason: json['reason'] as String?, - appliedRevision: (json['appliedRevision'] as num?)?.toInt(), - latestRevision: (json['latestRevision'] as num?)?.toInt(), - latestPayload: json['latestPayload'] == null - ? null - : Map.from(json['latestPayload'] as Map), - ); + final opId = json['opId'] as String; + return switch (json['status']) { + 'applied' => AppliedOpAck( + opId: opId, + revision: _requiredInt(json['appliedRevision']), + ), + 'noop' => NoopOpAck( + opId: opId, + currentRevision: (json['currentRevision'] as num?)?.toInt(), + ), + 'rejected' => RejectedOpAck( + opId: opId, + rejectionReason: + OpRejectionReason.fromWireName(json['reason'] as String), + current: json['current'] == null + ? null + : CurrentOpSnapshot.fromJson( + Map.from(json['current'] as Map), + ), + ), + 'failed' => FailedOpAck( + opId: opId, + code: json['code'] as String, + rawCode: json['rawCode'] as String, + message: json['message'] as String, + ), + final Object? status => + throw FormatException('Unknown op result status: $status'), + }; } } +final class AppliedOpAck extends OpAck { + const AppliedOpAck({required this.opId, required this.revision}); + @override + final String opId; + final int revision; +} + +final class NoopOpAck extends OpAck { + const NoopOpAck({required this.opId, this.currentRevision}); + @override + final String opId; + final int? currentRevision; +} + +final class RejectedOpAck extends OpAck { + const RejectedOpAck({ + required this.opId, + required this.rejectionReason, + this.current, + }); + @override + final String opId; + final OpRejectionReason rejectionReason; + final CurrentOpSnapshot? current; +} + +final class FailedOpAck extends OpAck { + const FailedOpAck({ + required this.opId, + required this.code, + required this.rawCode, + required this.message, + }); + @override + final String opId; + final String code; + final String rawCode; + final String message; +} + enum ConflictResolutionType { rebase, drop, retry } class ConflictResolution { @@ -241,25 +1158,6 @@ class RemoteStrategyHeader { final String? themeProfileId; final CloudPayload? themeOverridePalette; final String? role; - - factory RemoteStrategyHeader.fromJson(Map json) { - return RemoteStrategyHeader( - publicId: json['publicId'] as String, - name: json['name'] as String, - mapData: json['mapData'] as String, - revision: (json['revision'] as num?)?.toInt() ?? 0, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - updatedAt: DateTime.fromMillisecondsSinceEpoch( - (json['updatedAt'] as num?)?.toInt() ?? 0, - ), - themeProfileId: json['themeProfileId'] as String?, - themeOverridePalette: - cloudObjectPayloadOrNull(json['themeOverridePalette']), - role: json['role'] as String?, - ); - } } class RemotePage { @@ -282,23 +1180,6 @@ class RemotePage { final int revision; final DateTime createdAt; final DateTime updatedAt; - - factory RemotePage.fromJson(Map json) { - return RemotePage( - publicId: json['publicId'] as String, - strategyPublicId: json['strategyPublicId'] as String, - name: json['name'] as String, - sortIndex: (json['sortIndex'] as num).toInt(), - isAttack: json['isAttack'] as bool? ?? true, - revision: (json['revision'] as num?)?.toInt() ?? 0, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - updatedAt: DateTime.fromMillisecondsSinceEpoch( - (json['updatedAt'] as num?)?.toInt() ?? 0, - ), - ); - } } class RemotePageContent { @@ -313,19 +1194,6 @@ class RemotePageContent { final int revision; final DateTime createdAt; final DateTime updatedAt; - - factory RemotePageContent.fromJson(Map json) { - return RemotePageContent( - settings: cloudObjectPayloadOrNull(json['settings']), - revision: (json['revision'] as num?)?.toInt() ?? 0, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - updatedAt: DateTime.fromMillisecondsSinceEpoch( - (json['updatedAt'] as num?)?.toInt() ?? 0, - ), - ); - } } class RemoteElement { @@ -350,19 +1218,6 @@ class RemoteElement { final bool deleted; Map decodedPayload() => cloudPayloadData(payload); - - factory RemoteElement.fromJson(Map json) { - return RemoteElement( - publicId: json['publicId'] as String, - strategyPublicId: json['strategyPublicId'] as String, - pagePublicId: json['pagePublicId'] as String, - elementType: json['elementType'] as String, - payload: Map.from(json['payload'] as Map), - sortIndex: (json['sortIndex'] as num?)?.toInt() ?? 0, - revision: (json['revision'] as num?)?.toInt() ?? 0, - deleted: json['deleted'] as bool? ?? false, - ); - } } class RemoteLineup { @@ -383,18 +1238,6 @@ class RemoteLineup { final int sortIndex; final int revision; final bool deleted; - - factory RemoteLineup.fromJson(Map json) { - return RemoteLineup( - publicId: json['publicId'] as String, - strategyPublicId: json['strategyPublicId'] as String, - pagePublicId: json['pagePublicId'] as String, - payload: Map.from(json['payload'] as Map), - sortIndex: (json['sortIndex'] as num?)?.toInt() ?? 0, - revision: (json['revision'] as num?)?.toInt() ?? 0, - deleted: json['deleted'] as bool? ?? false, - ); - } } class RemoteImageAsset { @@ -423,26 +1266,6 @@ class RemoteImageAsset { final DateTime? uploadedAt; final String? url; final String? legacyStoragePath; - - factory RemoteImageAsset.fromJson(Map json) { - return RemoteImageAsset( - publicId: json['publicId'] as String, - provider: json['provider'] as String? ?? 'convex', - uploadStatus: json['uploadStatus'] as String? ?? 'active', - fileExtension: json['fileExtension'] as String? ?? '', - mimeType: json['mimeType'] as String?, - width: (json['width'] as num?)?.toInt(), - height: (json['height'] as num?)?.toInt(), - byteSize: (json['byteSize'] as num?)?.toInt(), - uploadedAt: json['uploadedAt'] == null - ? null - : DateTime.fromMillisecondsSinceEpoch( - (json['uploadedAt'] as num).toInt(), - ), - url: json['url'] as String?, - legacyStoragePath: json['legacyStoragePath'] as String?, - ); - } } class RemoteStrategyShell { @@ -558,96 +1381,6 @@ class RemoteFullStrategySnapshot { } } -class CloudStrategySummary { - const CloudStrategySummary({ - required this.publicId, - required this.name, - required this.mapData, - required this.revision, - required this.createdAt, - required this.updatedAt, - this.role, - this.attackLabel, - }); - - final String publicId; - final String name; - final String mapData; - final int revision; - final DateTime createdAt; - final DateTime updatedAt; - final String? role; - final String? attackLabel; - - factory CloudStrategySummary.fromJson(Map json) { - return CloudStrategySummary( - publicId: json['publicId'] as String, - name: json['name'] as String, - mapData: json['mapData'] as String, - revision: (json['revision'] as num?)?.toInt() ?? 0, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - updatedAt: DateTime.fromMillisecondsSinceEpoch( - (json['updatedAt'] as num?)?.toInt() ?? 0, - ), - role: json['role'] as String?, - attackLabel: json['attackLabel'] as String?, - ); - } -} - -class CloudFolderSummary { - const CloudFolderSummary({ - required this.publicId, - required this.name, - required this.createdAt, - required this.updatedAt, - this.role, - this.parentFolderPublicId, - this.iconId, - this.iconCodePoint, - this.iconFontFamily, - this.iconFontPackage, - this.color, - this.customColorValue, - }); - - final String publicId; - final String name; - final DateTime createdAt; - final DateTime updatedAt; - final String? role; - final String? parentFolderPublicId; - final int? iconId; - final int? iconCodePoint; - final String? iconFontFamily; - final String? iconFontPackage; - final String? color; - final int? customColorValue; - - factory CloudFolderSummary.fromJson(Map json) { - return CloudFolderSummary( - publicId: json['publicId'] as String, - name: json['name'] as String, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - updatedAt: DateTime.fromMillisecondsSinceEpoch( - (json['updatedAt'] as num?)?.toInt() ?? 0, - ), - role: json['role'] as String?, - parentFolderPublicId: json['parentFolderPublicId'] as String?, - iconId: (json['iconId'] as num?)?.toInt(), - iconCodePoint: (json['iconCodePoint'] as num?)?.toInt(), - iconFontFamily: json['iconFontFamily'] as String?, - iconFontPackage: json['iconFontPackage'] as String?, - color: json['color'] as String?, - customColorValue: (json['customColorValue'] as num?)?.toInt(), - ); - } -} - class ShareLinkSummary { const ShareLinkSummary({ required this.token, @@ -662,19 +1395,18 @@ class ShareLinkSummary { final DateTime? revokedAt; bool get isRevoked => revokedAt != null; +} - factory ShareLinkSummary.fromJson(Map json) { - return ShareLinkSummary( - token: json['token'] as String, - role: json['role'] as String, - createdAt: DateTime.fromMillisecondsSinceEpoch( - (json['createdAt'] as num?)?.toInt() ?? 0, - ), - revokedAt: json['revokedAt'] == null - ? null - : DateTime.fromMillisecondsSinceEpoch( - (json['revokedAt'] as num).toInt(), - ), - ); - } +class ShareRedemption { + const ShareRedemption({ + required this.targetType, + required this.role, + this.folderPublicId, + this.strategyPublicId, + }); + + final String targetType; + final String role; + final String? folderPublicId; + final String? strategyPublicId; } diff --git a/lib/collab/convex_payload_codecs.dart b/lib/collab/convex_payload_codecs.dart new file mode 100644 index 00000000..538878bb --- /dev/null +++ b/lib/collab/convex_payload_codecs.dart @@ -0,0 +1,125 @@ +import 'package:icarus/collab/canonical_json.dart'; +import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/transport/convex_transport.dart'; + +export 'package:icarus/collab/collab_models.dart' show CloudPayload; + +final class ConvexPayload { + const ConvexPayload(this.tag); + + final String tag; +} + +abstract interface class ConvexPayloadCodec { + const ConvexPayloadCodec(); + + ConvexValue encode(T value); + + T decode(ConvexValue value); +} + +CloudPayload _decodePayload(ConvexValue value, String expectedTag) { + if (value is! ConvexObject) { + throw FormatException('Expected $expectedTag payload object'); + } + final decoded = value.toDart(); + if (decoded['kind'] != expectedTag) { + throw FormatException( + 'Expected payload kind $expectedTag, received ${decoded['kind']}', + ); + } + if (decoded['payloadVersion'] is! num || decoded['data'] is! Map) { + throw FormatException('Invalid $expectedTag payload envelope'); + } + return Map.from( + canonicalCloudJsonValue(decoded) as Map, + ); +} + +ConvexValue _encodePayload(CloudPayload value, String expectedTag) { + if (value['kind'] != expectedTag) { + throw FormatException( + 'Expected payload kind $expectedTag, received ${value['kind']}', + ); + } + return ConvexValue.fromDart(canonicalCloudJsonValue(value)); +} + +@ConvexPayload('agent') +final class AgentConvexCodec implements ConvexPayloadCodec { + const AgentConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'agent'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'agent'); +} + +@ConvexPayload('ability') +final class AbilityConvexCodec implements ConvexPayloadCodec { + const AbilityConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'ability'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'ability'); +} + +@ConvexPayload('drawing') +final class DrawingConvexCodec implements ConvexPayloadCodec { + const DrawingConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'drawing'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'drawing'); +} + +@ConvexPayload('text') +final class TextConvexCodec implements ConvexPayloadCodec { + const TextConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'text'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'text'); +} + +@ConvexPayload('image') +final class ImageConvexCodec implements ConvexPayloadCodec { + const ImageConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'image'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'image'); +} + +@ConvexPayload('utility') +final class UtilityConvexCodec implements ConvexPayloadCodec { + const UtilityConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => _decodePayload(value, 'utility'); + + @override + ConvexValue encode(CloudPayload value) => _encodePayload(value, 'utility'); +} + +@ConvexPayload('lineupGroup') +final class LineupGroupConvexCodec implements ConvexPayloadCodec { + const LineupGroupConvexCodec(); + + @override + CloudPayload decode(ConvexValue value) => + _decodePayload(value, 'lineupGroup'); + + @override + ConvexValue encode(CloudPayload value) => + _encodePayload(value, 'lineupGroup'); +} diff --git a/lib/collab/convex_strategy_repository.dart b/lib/collab/convex_strategy_repository.dart index b9950c47..a8fc292e 100644 --- a/lib/collab/convex_strategy_repository.dart +++ b/lib/collab/convex_strategy_repository.dart @@ -1,302 +1,116 @@ -import 'dart:async'; -import 'dart:convert'; - -import 'package:icarus/collab/convex_client.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:icarus/collab/cloud_media_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/convex_client.dart'; +import 'package:icarus/collab/generated/generated.dart'; +import 'package:icarus/collab/transport/convex_transport.dart'; +import 'package:icarus/collab/transport/convex_transport_adapter.dart'; +import 'package:icarus/const/maps.dart'; +import 'package:icarus/const/settings.dart'; +import 'package:icarus/domain/folder.dart'; +import 'package:icarus/providers/user_preferences_provider.dart'; +import 'package:icarus/strategy/strategy_models.dart'; final convexStrategyRepositoryProvider = Provider( - (ref) => ConvexStrategyRepository(ConvexClient.instance), + (ref) => ConvexStrategyRepository.fromClient(ConvexClient.instance), ); class ConvexStrategyRepository { - ConvexStrategyRepository(this._client); - - final ConvexClient _client; - - Object? _decodeJsonPayload(dynamic value) { - if (value is String) { - try { - return jsonDecode(value); - } catch (_) { - return value; - } - } - return value; - } + ConvexStrategyRepository(this._api); - Map _decodeObject(dynamic value) { - final decoded = _decodeJsonPayload(value); - if (decoded is Map) { - return decoded; - } - if (decoded is Map) { - return Map.from(decoded); - } - throw FormatException( - 'Expected object payload, received ${decoded.runtimeType}'); - } + factory ConvexStrategyRepository.fromClient(ConvexClient client) => + ConvexStrategyRepository( + IcarusConvexApi(PlatformConvexTransport(client)), + ); - List> _decodeObjectList(dynamic value) { - final decoded = _decodeJsonPayload(value); - if (decoded is! List) { - throw FormatException( - 'Expected list payload, received ${decoded.runtimeType}'); - } - - return decoded - .map((item) => _decodeJsonPayload(item)) - .whereType() - .map((item) => Map.from(item)) - .toList(growable: false); - } + final IcarusConvexApi _api; - Stream> watchAllFolders() { - return _watchList( - name: 'folders:listAll', - args: const {'scope': 'all'}, - fromJson: CloudFolderSummary.fromJson, - ); + Future ensureCurrentUser() async { + await _api.users.ensureCurrentUser(); } - Stream> watchFoldersForParent( - String? parentFolderPublicId, { - String scope = 'owned', - }) { - return _watchList( - name: 'folders:listForParent', - args: { - if (parentFolderPublicId != null) - 'parentFolderPublicId': parentFolderPublicId, - 'scope': scope, - }, - fromJson: CloudFolderSummary.fromJson, - ); + Stream> watchAllFolders() { + return _api.folders + .listTree( + scope: const ConvexOptional.present(FoldersListTreeArgsScope.all), + ) + .watch() + .map( + (folders) => folders.map(_folderEntry).toList(growable: false), + ); } - Stream> watchStrategiesForFolder( + Stream> watchStrategiesForFolder( String? folderPublicId, { String scope = 'owned', }) { - return _watchList( - name: 'strategies:listForFolder', - args: { - if (folderPublicId != null) 'folderPublicId': folderPublicId, - 'scope': scope, - }, - fromJson: CloudStrategySummary.fromJson, - ); - } - - Stream> watchSharedStrategies() { - return _watchList( - name: 'strategies:listSharedWithMe', - args: const {}, - fromJson: CloudStrategySummary.fromJson, - ); - } - - Stream> _watchList({ - required String name, - required Map args, - required T Function(Map) fromJson, - }) { - return _watch>( - name: name, - args: args, - decode: (value) => - _decodeObjectList(value).map(fromJson).toList(growable: false), - ); - } - - Stream _watchObject({ - required String name, - required Map args, - required T Function(Map) fromJson, - }) { - return _watch( - name: name, - args: args, - decode: (value) => fromJson(_decodeObject(value)), - ); + return _api.strategies + .listForFolder( + folderPublicId: _optional(folderPublicId), + scope: ConvexOptional.present(_folderScope(scope)), + ) + .watch() + .map( + (strategies) => + strategies.map(_strategyEntry).toList(growable: false), + ); } - Stream _watch({ - required String name, - required Map args, - required T Function(dynamic) decode, - }) { - final controller = StreamController.broadcast(); - SubscriptionHandle? subscription; - bool isListening = false; - int epoch = 0; - - Future start(int myEpoch) async { - try { - final nextSubscription = await _client.subscribe( - name: name, - args: args, - onUpdate: (value) { - if (!isListening || epoch != myEpoch) { - return; - } - try { - controller.add(decode(value)); - } catch (error, stackTrace) { - controller.addError(error, stackTrace); - } - }, - onError: (message, _) { - if (!isListening || epoch != myEpoch) { - return; - } - controller.addError(Exception('$name error: $message')); - }, + Stream> watchSharedStrategies() { + return _api.strategies.listSharedWithMe().watch().map( + (strategies) => + strategies.map(_strategyEntry).toList(growable: false), ); - - if (!isListening || epoch != myEpoch) { - try { - nextSubscription.cancel(); - } catch (_) {} - return; - } - - subscription = nextSubscription; - } catch (error, stackTrace) { - if (isListening && epoch == myEpoch) { - controller.addError(error, stackTrace); - } - } - } - - controller.onListen = () { - if (isListening) { - return; - } - isListening = true; - final myEpoch = ++epoch; - start(myEpoch); - }; - - controller.onCancel = () { - isListening = false; - try { - subscription?.cancel(); - } catch (_) {} - subscription = null; - epoch += 1; - }; - - return controller.stream; } Future fetchShell(String strategyPublicId) async { - final response = await _client.query('strategy:getShell', { - 'strategyPublicId': strategyPublicId, - }); - return _decodeShell(_decodeObject(response)); - } - - Stream watchShell(String strategyPublicId) { - return _watchObject( - name: 'strategy:getShell', - args: {'strategyPublicId': strategyPublicId}, - fromJson: _decodeShell, + return _strategyShell( + await _api.strategy.getShell(strategyPublicId: strategyPublicId).fetch(), ); } - RemoteStrategyShell _decodeShell(Map value) { - return RemoteStrategyShell( - header: RemoteStrategyHeader.fromJson(_decodeObject(value['header'])), - pages: _decodeObjectList(value['pages']) - .map(RemotePage.fromJson) - .toList(growable: false), - ); + Stream watchShell(String strategyPublicId) { + return _api.strategy + .getShell(strategyPublicId: strategyPublicId) + .watch() + .map(_strategyShell); } Future fetchPageSnapshot({ required String strategyPublicId, required String pagePublicId, }) async { - final response = await _client.query('page:getSnapshot', { - 'strategyPublicId': strategyPublicId, - 'pagePublicId': pagePublicId, - }); - return _decodePageSnapshot(_decodeObject(response)); + return _pageSnapshot( + await _api.page + .getSnapshot( + strategyPublicId: strategyPublicId, + pagePublicId: pagePublicId, + ) + .fetch(), + ); } Stream watchPageSnapshot({ required String strategyPublicId, required String pagePublicId, }) { - return _watchObject( - name: 'page:getSnapshot', - args: { - 'strategyPublicId': strategyPublicId, - 'pagePublicId': pagePublicId, - }, - fromJson: _decodePageSnapshot, - ); - } - - RemotePageSnapshot _decodePageSnapshot(Map value) { - final assets = _decodeObjectList(value['assets']) - .map(RemoteImageAsset.fromJson) - .toList(growable: false); - return RemotePageSnapshot( - page: RemotePage.fromJson(_decodeObject(value['page'])), - content: RemotePageContent.fromJson(_decodeObject(value['content'])), - elements: _decodeObjectList(value['elements']) - .map(RemoteElement.fromJson) - .toList(growable: false), - lineups: _decodeObjectList(value['lineups']) - .map(RemoteLineup.fromJson) - .toList(growable: false), - assetsById: {for (final asset in assets) asset.publicId: asset}, - ); + return _api.page + .getSnapshot( + strategyPublicId: strategyPublicId, + pagePublicId: pagePublicId, + ) + .watch() + .map(_pageSnapshot); } Future fetchFullSnapshot( String strategyPublicId, ) async { - final response = await _client.query('strategy:getFullSnapshot', { - 'strategyPublicId': strategyPublicId, - }); - return _decodeFullSnapshot(_decodeObject(response)); - } - - RemoteFullStrategySnapshot _decodeFullSnapshot(Map value) { - final pages = _decodeObjectList(value['pages']).map((json) { - final page = RemotePage.fromJson(json); - return RemoteFullPage( - page: page, - content: RemotePageContent.fromJson({ - 'settings': json['settings'], - 'revision': json['contentRevision'], - 'createdAt': json['contentCreatedAt'], - 'updatedAt': json['contentUpdatedAt'], - }), - ); - }).toList(growable: false); - final elements = _decodeObjectList(value['elements']) - .map(RemoteElement.fromJson) - .toList(growable: false); - final lineups = _decodeObjectList(value['lineups']) - .map(RemoteLineup.fromJson) - .toList(growable: false); - final assets = _decodeObjectList(value['assets']) - .map(RemoteImageAsset.fromJson) - .toList(growable: false); - - return RemoteFullStrategySnapshot( - header: RemoteStrategyHeader.fromJson(_decodeObject(value['header'])), - pages: pages, - elementsByPage: RemoteFullStrategySnapshot.groupElementsByPage(elements), - lineupsByPage: RemoteFullStrategySnapshot.groupLineupsByPage(lineups), - assetsById: { - for (final asset in assets) asset.publicId: asset, - }, + return _fullSnapshot( + await _api.strategy + .getFullSnapshot(strategyPublicId: strategyPublicId) + .fetch(), ); } @@ -309,19 +123,24 @@ class ConvexStrategyRepository { int? width, int? height, }) async { - final response = await _client.action( - name: 'images:generateUploadUrl', - args: { - 'strategyPublicId': strategyPublicId, - 'assetPublicId': assetPublicId, - 'mimeType': mimeType, - 'fileExtension': fileExtension, - if (byteSize != null) 'byteSize': byteSize, - if (width != null) 'width': width, - if (height != null) 'height': height, - }, + final result = await _api.images.generateUploadUrl( + strategyPublicId: strategyPublicId, + assetPublicId: assetPublicId, + mimeType: mimeType, + fileExtension: fileExtension, + byteSize: _optionalNumber(byteSize), + width: _optionalNumber(width), + height: _optionalNumber(height), + ); + return CloudImageUploadIntent( + provider: result.provider.wireName, + uploadId: result.uploadId, + objectKey: result.objectKey, + uploadUrl: result.uploadUrl, + requiredHeaders: result.requiredHeaders, + expiresAt: _dateTime(result.expiresAt), + maxBytes: result.maxBytes.toInt(), ); - return CloudImageUploadIntent.fromJson(_decodeObject(response)); } Future completeImageUpload({ @@ -338,22 +157,21 @@ class ConvexStrategyRepository { int? width, int? height, }) async { - await _client.action( - name: 'images:completeUpload', - args: { - 'strategyPublicId': strategyPublicId, - 'assetPublicId': assetPublicId, - if (provider != null) 'provider': provider, - if (uploadId != null) 'uploadId': uploadId, - if (objectKey != null) 'objectKey': objectKey, - if (storageId != null) 'storageId': storageId, - if (etag != null) 'etag': etag, - if (mimeType != null) 'mimeType': mimeType, - if (fileExtension != null) 'fileExtension': fileExtension, - if (byteSize != null) 'byteSize': byteSize, - if (width != null) 'width': width, - if (height != null) 'height': height, - }, + await _api.images.completeUpload( + strategyPublicId: strategyPublicId, + assetPublicId: assetPublicId, + provider: provider == null + ? const ConvexOptional.absent() + : ConvexOptional.present(_imageProvider(provider)), + uploadId: _optional(uploadId), + objectKey: _optional(objectKey), + storageId: _optional(storageId), + etag: _optional(etag), + mimeType: _optional(mimeType), + fileExtension: _optional(fileExtension), + byteSize: _optionalNumber(byteSize), + width: _optionalNumber(width), + height: _optionalNumber(height), ); } @@ -361,14 +179,13 @@ class ConvexStrategyRepository { required String strategyPublicId, required String assetPublicId, }) async { - final response = await _client.query( - 'images:getAssetUrl', - { - 'strategyPublicId': strategyPublicId, - 'assetPublicId': assetPublicId, - }, - ); - return _decodeObject(response)['url'] as String?; + final result = await _api.images + .getAssetUrl( + strategyPublicId: strategyPublicId, + assetPublicId: assetPublicId, + ) + .fetch(); + return result.url; } Future> applyBatch({ @@ -376,26 +193,23 @@ class ConvexStrategyRepository { required String clientId, required List ops, }) async { - if (ops.isEmpty) { - return const []; - } - - final response = await _client.mutation( - name: 'ops:applyBatch', - args: { - 'strategyPublicId': strategyPublicId, - 'clientId': clientId, - 'clientProtocolVersion': currentCloudProtocolVersion, - 'ops': ops.map((op) => op.toConvexJson()).toList(growable: false), - }, - ); - - final resultList = - (_decodeObject(response)['results'] as List?) ?? const []; - return resultList - .whereType() - .map((item) => OpAck.fromJson(Map.from(item))) + if (ops.isEmpty) return const []; + + final typedOps = ops.indexed + .map( + (entry) => OpsApplyBatchArgsOpsItem.decode( + ConvexValue.fromDart(entry.$2.toConvexJson()), + 'ops[${entry.$1}]', + ), + ) .toList(growable: false); + final result = await _api.ops.applyBatch( + strategyPublicId: strategyPublicId, + clientId: clientId, + clientProtocolVersion: currentCloudProtocolVersion.toDouble(), + ops: typedOps, + ); + return result.results.map(_opAck).toList(growable: false); } Future createFolder({ @@ -409,20 +223,58 @@ class ConvexStrategyRepository { String? color, int? customColorValue, }) async { - await _client.mutation( - name: 'folders:create', - args: { - 'publicId': publicId, - 'name': name, - if (parentFolderPublicId != null) - 'parentFolderPublicId': parentFolderPublicId, - if (iconId != null) 'iconId': iconId, - if (iconCodePoint != null) 'iconCodePoint': iconCodePoint, - if (iconFontFamily != null) 'iconFontFamily': iconFontFamily, - if (iconFontPackage != null) 'iconFontPackage': iconFontPackage, - if (color != null) 'color': color, - if (customColorValue != null) 'customColorValue': customColorValue, - }, + await _api.folders.create( + publicId: publicId, + name: name, + parentFolderPublicId: _optional(parentFolderPublicId), + iconId: _optionalNumber(iconId), + iconCodePoint: _optionalNumber(iconCodePoint), + iconFontFamily: _optional(iconFontFamily), + iconFontPackage: _optional(iconFontPackage), + color: _optional(color), + customColorValue: _optionalNumber(customColorValue), + ); + } + + Future updateFolder({ + required String folderPublicId, + String? name, + int? iconId, + int? iconCodePoint, + String? iconFontFamily, + String? iconFontPackage, + bool clearIconFontFamily = false, + bool clearIconFontPackage = false, + String? color, + int? customColorValue, + bool clearCustomColorValue = false, + }) async { + await _api.folders.update( + folderPublicId: folderPublicId, + name: _optional(name), + iconId: _optionalNumber(iconId), + iconCodePoint: _optionalNumber(iconCodePoint), + iconFontFamily: _optional(iconFontFamily), + iconFontPackage: _optional(iconFontPackage), + clearIconFontFamily: _presentWhenTrue(clearIconFontFamily), + clearIconFontPackage: _presentWhenTrue(clearIconFontPackage), + color: _optional(color), + customColorValue: _optionalNumber(customColorValue), + clearCustomColorValue: _presentWhenTrue(clearCustomColorValue), + ); + } + + Future deleteFolder(String folderPublicId) async { + await _api.folders.delete(folderPublicId: folderPublicId); + } + + Future moveFolder({ + required String folderPublicId, + String? parentFolderPublicId, + }) async { + await _api.folders.move( + folderPublicId: folderPublicId, + parentFolderPublicId: _optional(parentFolderPublicId), ); } @@ -434,17 +286,13 @@ class ConvexStrategyRepository { String? themeProfileId, Map? themeOverridePalette, }) async { - await _client.mutation( - name: 'strategies:create', - args: { - 'publicId': publicId, - 'name': name, - 'mapData': mapData, - if (folderPublicId != null) 'folderPublicId': folderPublicId, - if (themeProfileId != null) 'themeProfileId': themeProfileId, - if (themeOverridePalette != null) - 'themeOverridePalette': themeOverridePalette, - }, + await _api.strategies.create( + publicId: publicId, + name: name, + mapData: mapData, + folderPublicId: _optional(folderPublicId), + themeProfileId: _optional(themeProfileId), + themeOverridePalette: _themePalette(themeOverridePalette), ); } @@ -460,22 +308,71 @@ class ConvexStrategyRepository { Map? themeOverridePalette, Map? initialPageSettings, }) async { - await _client.mutation( - name: 'strategies:createWithInitialPage', - args: { - 'publicId': publicId, - 'name': name, - 'mapData': mapData, - 'initialPagePublicId': initialPagePublicId, - 'initialPageName': initialPageName, - 'initialPageIsAttack': initialPageIsAttack, - if (folderPublicId != null) 'folderPublicId': folderPublicId, - if (themeProfileId != null) 'themeProfileId': themeProfileId, - if (themeOverridePalette != null) - 'themeOverridePalette': themeOverridePalette, - if (initialPageSettings != null) - 'initialPageSettings': initialPageSettings, - }, + await _api.strategies.createWithInitialPage( + publicId: publicId, + name: name, + mapData: mapData, + initialPagePublicId: initialPagePublicId, + initialPageName: initialPageName, + initialPageIsAttack: initialPageIsAttack, + folderPublicId: _optional(folderPublicId), + themeProfileId: _optional(themeProfileId), + themeOverridePalette: _themePalette(themeOverridePalette), + initialPageSettings: _pageSettings(initialPageSettings), + ); + } + + Future updateStrategyName({ + required String strategyPublicId, + required String name, + required int expectedRevision, + }) async { + await _api.strategies.update( + strategyPublicId: strategyPublicId, + name: ConvexOptional.present(name), + expectedRevision: expectedRevision.toDouble(), + ); + } + + Future deleteStrategy({ + required String strategyPublicId, + required int expectedRevision, + }) async { + await _api.strategies.delete( + strategyPublicId: strategyPublicId, + expectedRevision: expectedRevision.toDouble(), + ); + } + + Future moveStrategy({ + required String strategyPublicId, + required String? folderPublicId, + required int expectedRevision, + }) async { + await _api.strategies.move( + strategyPublicId: strategyPublicId, + folderPublicId: _optional(folderPublicId), + expectedRevision: expectedRevision.toDouble(), + ); + } + + Future addPage({ + required String strategyPublicId, + required String pagePublicId, + required String name, + required int sortIndex, + required bool isAttack, + required int expectedRevision, + Map? settings, + }) async { + await _api.pages.add( + strategyPublicId: strategyPublicId, + pagePublicId: pagePublicId, + name: name, + sortIndex: sortIndex.toDouble(), + isAttack: isAttack, + expectedRevision: expectedRevision.toDouble(), + settings: _pageSettings(settings), ); } @@ -483,12 +380,22 @@ class ConvexStrategyRepository { required String targetType, required String targetPublicId, }) async { - final response = await _client.query('shares:list', { - 'targetType': targetType, - 'targetPublicId': targetPublicId, - }); - return _decodeObjectList(response) - .map(ShareLinkSummary.fromJson) + final result = await _api.shares + .list( + targetType: _shareTargetType(targetType), + targetPublicId: targetPublicId, + ) + .fetch(); + return result + .map( + (share) => ShareLinkSummary( + token: share.token, + role: share.role.wireName, + createdAt: _dateTime(share.createdAt), + revokedAt: + share.revokedAt == null ? null : _dateTime(share.revokedAt!), + ), + ) .toList(growable: false); } @@ -498,14 +405,11 @@ class ConvexStrategyRepository { required String token, required String role, }) async { - await _client.mutation( - name: 'shares:create', - args: { - 'targetType': targetType, - 'targetPublicId': targetPublicId, - 'token': token, - 'role': role, - }, + await _api.shares.create( + targetType: _shareTargetType(targetType), + targetPublicId: targetPublicId, + token: token, + role: _shareRole(role), ); } @@ -514,21 +418,404 @@ class ConvexStrategyRepository { required String targetPublicId, required String token, }) async { - await _client.mutation( - name: 'shares:revoke', - args: { - 'targetType': targetType, - 'targetPublicId': targetPublicId, - 'token': token, - }, + await _api.shares.revoke( + targetType: _shareTargetType(targetType), + targetPublicId: targetPublicId, + token: token, ); } - Future> redeemShareLink(String token) async { - final response = await _client.mutation( - name: 'shares:redeem', - args: {'token': token}, - ); - return _decodeObject(response); + Future redeemShareLink(String token) async { + final result = await _api.shares.redeem(token: token); + return switch (result) { + SharesRedeemResultFolder(:final folderPublicId, :final role) => + ShareRedemption( + targetType: 'folder', + folderPublicId: folderPublicId, + role: role.wireName, + ), + SharesRedeemResultStrategy( + :final folderPublicId, + :final strategyPublicId, + :final role, + ) => + ShareRedemption( + targetType: 'strategy', + folderPublicId: folderPublicId, + strategyPublicId: strategyPublicId, + role: role.wireName, + ), + }; + } +} + +bool isTypedConvexUnauthenticatedError(Object error) { + return (error is ConvexFunctionException && + error.code == ConvexErrorCode.unauthenticated) || + (error is ConvexClientFunctionError && + error.rawCode == ConvexErrorCode.unauthenticated.wireName); +} + +CloudFolderEntry _folderEntry(FoldersListTreeResultItem folder) { + return ( + folder: Folder( + id: folder.publicId, + name: folder.name, + dateCreated: _dateTime(folder.createdAt), + parentID: folder.parentFolderPublicId, + iconId: folderIconIdFromCloud( + iconId: folder.iconId?.toInt(), + codePoint: folder.iconCodePoint?.toInt(), + fontFamily: folder.iconFontFamily, + fontPackage: folder.iconFontPackage, + ), + color: folderColorFromWireName(folder.color), + customColor: folderCustomColorFromCloud(folder.customColorValue?.toInt()), + ), + role: folder.role.wireName, + ); +} + +CloudStrategyEntry _strategyEntry( + StrategiesListForFolderResultItem strategy, +) { + return ( + strategy: StrategyData( + id: strategy.publicId, + name: strategy.name, + mapData: _mapValue(strategy.mapData), + versionNumber: Settings.versionNumber, + lastEdited: _dateTime(strategy.updatedAt), + createdAt: _dateTime(strategy.createdAt), + folderID: strategy.folderPublicId, + themeProfileId: strategy.themeProfileId, + themeOverridePalette: _mapThemePalette(strategy.themeOverridePalette), + ), + revision: strategy.revision.toInt(), + role: strategy.role.wireName, + attackLabel: strategy.attackLabel.wireName, + ); +} + +MapValue _mapValue(String wireName) { + for (final entry in Maps.mapNames.entries) { + if (entry.value == wireName) return entry.key; } + throw ConvexDecodingException( + 'strategies.listForFolder.result.mapData', + 'unknown Icarus map $wireName', + ); +} + +MapThemePalette? _mapThemePalette( + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette? palette, +) { + final value = _themePaletteValue(palette); + return value == null ? null : MapThemePalette.fromJson(value); +} + +RemoteStrategyShell _strategyShell(StrategyGetShellResult result) { + return RemoteStrategyShell( + header: _strategyHeader(result.header), + pages: result.pages.map(_page).toList(growable: false), + ); } + +RemotePageSnapshot _pageSnapshot(PageGetSnapshotResult result) { + final assets = result.assets.map(_imageAsset).toList(growable: false); + return RemotePageSnapshot( + page: _page(result.page), + content: _pageContent(result.content), + elements: result.elements.map(_element).toList(growable: false), + lineups: result.lineups.map(_lineup).toList(growable: false), + assetsById: {for (final asset in assets) asset.publicId: asset}, + ); +} + +RemoteFullStrategySnapshot _fullSnapshot(StrategyGetFullSnapshotResult result) { + final pages = result.pages.map((page) { + return RemoteFullPage( + page: RemotePage( + publicId: page.publicId, + strategyPublicId: page.strategyPublicId, + name: page.name, + sortIndex: page.sortIndex.toInt(), + isAttack: page.isAttack, + revision: page.revision.toInt(), + createdAt: _dateTime(page.createdAt), + updatedAt: _dateTime(page.updatedAt), + ), + content: RemotePageContent( + settings: _settingsValue(page.settings), + revision: page.contentRevision.toInt(), + createdAt: _dateTime(page.contentCreatedAt), + updatedAt: _dateTime(page.contentUpdatedAt), + ), + ); + }).toList(growable: false); + final elements = result.elements.map(_element).toList(growable: false); + final lineups = result.lineups.map(_lineup).toList(growable: false); + final assets = result.assets.map(_imageAsset).toList(growable: false); + return RemoteFullStrategySnapshot( + header: _strategyHeader(result.header), + pages: pages, + elementsByPage: RemoteFullStrategySnapshot.groupElementsByPage(elements), + lineupsByPage: RemoteFullStrategySnapshot.groupLineupsByPage(lineups), + assetsById: {for (final asset in assets) asset.publicId: asset}, + ); +} + +RemoteStrategyHeader _strategyHeader(StrategiesGetHeaderResult header) { + return RemoteStrategyHeader( + publicId: header.publicId, + name: header.name, + mapData: header.mapData, + revision: header.revision.toInt(), + createdAt: _dateTime(header.createdAt), + updatedAt: _dateTime(header.updatedAt), + themeProfileId: header.themeProfileId, + themeOverridePalette: _themePaletteValue(header.themeOverridePalette), + role: header.role.wireName, + ); +} + +RemotePage _page(PageGetSnapshotResultPage page) { + return RemotePage( + publicId: page.publicId, + strategyPublicId: page.strategyPublicId, + name: page.name, + sortIndex: page.sortIndex.toInt(), + isAttack: page.isAttack, + revision: page.revision.toInt(), + createdAt: _dateTime(page.createdAt), + updatedAt: _dateTime(page.updatedAt), + ); +} + +RemotePageContent _pageContent(PageGetSnapshotResultContent content) { + return RemotePageContent( + settings: _settingsValue(content.settings), + revision: content.revision.toInt(), + createdAt: _dateTime(content.createdAt), + updatedAt: _dateTime(content.updatedAt), + ); +} + +RemoteElement _element(ElementsListForPageResultItem element) { + return RemoteElement( + publicId: element.publicId, + strategyPublicId: element.strategyPublicId, + pagePublicId: element.pagePublicId, + elementType: element.elementType.wireName, + payload: element.payload, + sortIndex: element.sortIndex.toInt(), + revision: element.revision.toInt(), + deleted: element.deleted, + ); +} + +RemoteLineup _lineup(LineupsListForPageResultItem lineup) { + return RemoteLineup( + publicId: lineup.publicId, + strategyPublicId: lineup.strategyPublicId, + pagePublicId: lineup.pagePublicId, + payload: lineup.payload, + sortIndex: lineup.sortIndex.toInt(), + revision: lineup.revision.toInt(), + deleted: lineup.deleted, + ); +} + +RemoteImageAsset _imageAsset(ImagesListForStrategyResultItem asset) { + return RemoteImageAsset( + publicId: asset.publicId, + provider: asset.provider.wireName, + uploadStatus: asset.uploadStatus.wireName, + fileExtension: asset.fileExtension, + mimeType: asset.mimeType, + width: asset.width?.toInt(), + height: asset.height?.toInt(), + byteSize: asset.byteSize?.toInt(), + uploadedAt: asset.uploadedAt == null ? null : _dateTime(asset.uploadedAt!), + url: asset.url, + legacyStoragePath: asset.legacyStoragePath, + ); +} + +OpAck _opAck(OpsApplyBatchResultResultsItem result) { + return switch (result) { + OpsApplyBatchResultResultsItemApplied( + :final opId, + :final appliedRevision + ) => + AppliedOpAck(opId: opId, revision: appliedRevision.toInt()), + OpsApplyBatchResultResultsItemNoop(:final opId, :final currentRevision) => + NoopOpAck( + opId: opId, + currentRevision: + currentRevision.isPresent ? currentRevision.value.toInt() : null, + ), + OpsApplyBatchResultResultsItemRejected( + :final opId, + :final reason, + :final current, + ) => + RejectedOpAck( + opId: opId, + rejectionReason: OpRejectionReason.fromWireName(reason.wireName), + current: current.isPresent ? _currentSnapshot(current.value) : null, + ), + OpsApplyBatchResultResultsItemFailed( + :final opId, + :final code, + :final rawCode, + :final message, + ) => + FailedOpAck( + opId: opId, + code: code, + rawCode: rawCode, + message: message, + ), + }; +} + +CurrentOpSnapshot _currentSnapshot( + OpsApplyBatchResultResultsItemRejectedCurrent current, +) { + return switch (current) { + OpsApplyBatchResultResultsItemRejectedCurrentStrategy( + :final revision, + :final value, + ) => + StrategyCurrentSnapshot( + revision: revision.toInt(), + value: { + 'name': value.name, + 'mapData': value.mapData, + 'themeProfileId': value.themeProfileId, + 'themeOverridePalette': _themePaletteValue( + value.themeOverridePalette, + ), + }, + ), + OpsApplyBatchResultResultsItemRejectedCurrentPage( + :final revision, + :final value, + ) => + PageCurrentSnapshot( + revision: revision.toInt(), + value: { + 'name': value.name, + 'sortIndex': value.sortIndex, + 'isAttack': value.isAttack, + }, + ), + OpsApplyBatchResultResultsItemRejectedCurrentPageContent( + :final revision, + :final value, + ) => + PageContentCurrentSnapshot( + revision: revision.toInt(), + value: {'settings': _settingsValue(value.settings)}, + ), + OpsApplyBatchResultResultsItemRejectedCurrentElement( + :final revision, + :final value, + ) => + ElementCurrentSnapshot(revision: revision.toInt(), value: value), + OpsApplyBatchResultResultsItemRejectedCurrentLineup( + :final revision, + :final value, + ) => + LineupCurrentSnapshot(revision: revision.toInt(), value: value), + }; +} + +CloudPayload? _themePaletteValue( + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette? palette, +) { + return palette == null ? null : _object(palette.encode('theme')); +} + +CloudPayload? _settingsValue( + OpsApplyBatchArgsOpsItemPageAddPayloadSettings? settings, +) { + return settings == null ? null : _object(settings.encode('settings')); +} + +Map _object(ConvexObject value) { + return Map.from(value.toDart()); +} + +DateTime _dateTime(double milliseconds) => + DateTime.fromMillisecondsSinceEpoch(milliseconds.toInt()); + +ConvexOptional _optional(T? value) => value == null + ? const ConvexOptional.absent() + : ConvexOptional.present(value); + +ConvexOptional _optionalNumber(num? value) => value == null + ? const ConvexOptional.absent() + : ConvexOptional.present(value.toDouble()); + +ConvexOptional _presentWhenTrue(bool value) => + value ? const ConvexOptional.present(true) : const ConvexOptional.absent(); + +ConvexOptional + _themePalette(Map? value) { + if (value == null) return const ConvexOptional.absent(); + return ConvexOptional.present( + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + ConvexValue.fromDart(value), + 'themeOverridePalette', + ), + ); +} + +ConvexOptional _pageSettings( + Map? value, +) { + if (value == null) return const ConvexOptional.absent(); + return ConvexOptional.present( + OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + ConvexValue.fromDart(value), + 'settings', + ), + ); +} + +FoldersListTreeArgsScope _folderScope(String value) => switch (value) { + 'all' => FoldersListTreeArgsScope.all, + 'owned' => FoldersListTreeArgsScope.owned, + 'shared' => FoldersListTreeArgsScope.shared, + _ => + throw ArgumentError.value(value, 'scope', 'Unsupported folder scope'), + }; + +ImagesCompleteUploadArgsProvider _imageProvider(String value) => + switch (value) { + 'convex' => ImagesCompleteUploadArgsProvider.convex, + 'r2' => ImagesCompleteUploadArgsProvider.r2, + _ => throw ArgumentError.value( + value, + 'provider', + 'Unsupported image provider', + ), + }; + +SharesCreateArgsTargetType _shareTargetType(String value) => switch (value) { + 'folder' => SharesCreateArgsTargetType.folder, + 'strategy' => SharesCreateArgsTargetType.strategy, + _ => throw ArgumentError.value( + value, + 'targetType', + 'Unsupported share target type', + ), + }; + +InvitesCreateArgsRole _shareRole(String value) => switch (value) { + 'editor' => InvitesCreateArgsRole.editor, + 'viewer' => InvitesCreateArgsRole.viewer, + _ => throw ArgumentError.value(value, 'role', 'Unsupported share role'), + }; diff --git a/lib/collab/durable_strategy_outbox.dart b/lib/collab/durable_strategy_outbox.dart index f09aea2d..baf80e56 100644 --- a/lib/collab/durable_strategy_outbox.dart +++ b/lib/collab/durable_strategy_outbox.dart @@ -6,7 +6,15 @@ import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/hive_boxes.dart'; import 'package:icarus/providers/collab/active_page_live_sync_models.dart'; -const durableOutboxRecordVersion = 1; +const durableOutboxRecordVersion = 2; +const durableOutboxVersionKey = '__outbox_record_version__'; + +Future prepareDurableStrategyOutbox() async { + final box = Hive.box(HiveBoxNames.strategyOutboxBox); + if (box.get(durableOutboxVersionKey) == durableOutboxRecordVersion) return; + await box.clear(); + await box.put(durableOutboxVersionKey, durableOutboxRecordVersion); +} enum DurableOutboxStatus { queued, inFlight, paused, attention } @@ -178,6 +186,7 @@ class HiveDurableStrategyOutboxStore implements DurableStrategyOutboxStore { final issues = []; for (final key in _box.keys) { final storageKey = key.toString(); + if (storageKey == durableOutboxVersionKey) continue; try { final raw = _box.get(key); final decoded = raw is String ? jsonDecode(raw) : raw; diff --git a/lib/collab/generated/convex_error_codes.dart b/lib/collab/generated/convex_error_codes.dart new file mode 100644 index 00000000..b9be89f1 --- /dev/null +++ b/lib/collab/generated/convex_error_codes.dart @@ -0,0 +1,80 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated from convex/function_spec.json by tool/icarus_convex_codegen. +// ignore_for_file: prefer_const_constructors, unused_element, unused_import + +import '../transport/convex_transport.dart'; + +enum ConvexErrorCode { + clientUpgradeRequired('CLIENT_UPGRADE_REQUIRED'), + conflict('CONFLICT'), + elementStrategyMismatch('ELEMENT_STRATEGY_MISMATCH'), + elementTypePayloadKindMismatch('ELEMENT_TYPE_PAYLOAD_KIND_MISMATCH'), + forbidden('FORBIDDEN'), + internalError('INTERNAL_ERROR'), + invalidElementPayloadData('INVALID_ELEMENT_PAYLOAD_DATA'), + invalidElementPayloadKind('INVALID_ELEMENT_PAYLOAD_KIND'), + invalidElementPayloadVersion('INVALID_ELEMENT_PAYLOAD_VERSION'), + invalidLineupPayloadData('INVALID_LINEUP_PAYLOAD_DATA'), + invalidLineupPayloadKind('INVALID_LINEUP_PAYLOAD_KIND'), + invalidLineupPayloadVersion('INVALID_LINEUP_PAYLOAD_VERSION'), + invalidOp('INVALID_OP'), + invalidPageContentCount('INVALID_PAGE_CONTENT_COUNT'), + invalidPayload('INVALID_PAYLOAD'), + inviteExpired('INVITE_EXPIRED'), + inviteRevoked('INVITE_REVOKED'), + lineupStrategyMismatch('LINEUP_STRATEGY_MISMATCH'), + missingAddElementArgs('MISSING_ADD_ELEMENT_ARGS'), + missingAddLineupArgs('MISSING_ADD_LINEUP_ARGS'), + missingElementPayload('MISSING_ELEMENT_PAYLOAD'), + missingEntityPublicId('MISSING_ENTITY_PUBLIC_ID'), + missingLineupPayload('MISSING_LINEUP_PAYLOAD'), + missingPageId('MISSING_PAGE_ID'), + missingPagePublicId('MISSING_PAGE_PUBLIC_ID'), + notFound('NOT_FOUND'), + pageDescriptorRequiresPageOp('PAGE_DESCRIPTOR_REQUIRES_PAGE_OP'), + pageSettingsRequirePageContent('PAGE_SETTINGS_REQUIRE_PAGE_CONTENT'), + pageStrategyMismatch('PAGE_STRATEGY_MISMATCH'), + r2ObjectKeyMismatch('R2_OBJECT_KEY_MISMATCH'), + shareLinkRevoked('SHARE_LINK_REVOKED'), + unauthenticated('UNAUTHENTICATED'), + unsupportedOp('UNSUPPORTED_OP'), + uploadIntentNotFound('UPLOAD_INTENT_NOT_FOUND'), + unknown('UNKNOWN'); + + const ConvexErrorCode(this.wireName); + final String wireName; + + static ConvexErrorCode fromWireName(String rawCode) { + for (final code in values) { + if (code != unknown && code.wireName == rawCode) return code; + } + return unknown; + } +} + +final class ConvexFunctionException implements Exception { + const ConvexFunctionException({ + required this.code, + required this.rawCode, + required this.message, + this.data, + }); + + factory ConvexFunctionException.fromTransport(ConvexTransportError error) => + ConvexFunctionException( + code: ConvexErrorCode.fromWireName(error.rawCode), + rawCode: error.rawCode, + message: error.message, + data: error.data, + ); + + final ConvexErrorCode code; + final String rawCode; + final String message; + final ConvexValue? data; + + @override + String toString() => + 'ConvexFunctionException(' + '$rawCode, $message)'; +} diff --git a/lib/collab/generated/convex_models.dart b/lib/collab/generated/convex_models.dart new file mode 100644 index 00000000..5f25547a --- /dev/null +++ b/lib/collab/generated/convex_models.dart @@ -0,0 +1,5227 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated from convex/function_spec.json by tool/icarus_convex_codegen. +// ignore_for_file: prefer_const_constructors, unused_element, unused_import + +import 'dart:typed_data'; + +import '../convex_payload_codecs.dart'; +import '../transport/convex_transport.dart'; + +final class ConvexOptional { + const ConvexOptional.absent() : isPresent = false, _value = null; + const ConvexOptional.present(T value) : isPresent = true, _value = value; + + final bool isPresent; + final T? _value; + + T get value { + if (!isPresent) throw StateError('Optional value is absent'); + return _value as T; + } +} + +final class ConvexDecodingException extends FormatException { + ConvexDecodingException(this.path, String message) : super('$path: $message'); + final String path; +} + +final class ConvexEncodingException extends FormatException { + ConvexEncodingException(this.path, String message) : super('$path: $message'); + final String path; +} + +Never _missing(String path, String field) => + throw ConvexDecodingException('$path.$field', 'missing required field'); + +String _fieldPath(String path, String field) => '$path.$field'; + +String _indexPath(String path, int index) => '$path[$index]'; + +void _checkObjectFields(ConvexObject object, String path, Set allowed) { + for (final field in object.value.keys) { + if (!allowed.contains(field)) { + throw ConvexDecodingException('$path.$field', 'unexpected field'); + } + } +} + +Null _decodeNull(ConvexValue value, String path) { + if (value is ConvexNull) return null; + throw ConvexDecodingException(path, 'expected null'); +} + +bool _decodeBoolean(ConvexValue value, String path) { + if (value case ConvexBoolean(:final value)) return value; + throw ConvexDecodingException(path, 'expected boolean'); +} + +double _decodeNumber(ConvexValue value, String path) { + if (value case ConvexFloat(:final value)) return value; + if (value case ConvexInteger(:final value)) return value.toDouble(); + throw ConvexDecodingException(path, 'expected number'); +} + +ConvexValue _encodeNumber(double value, String path) { + return ConvexFloat(value); +} + +BigInt _decodeBigInt(ConvexValue value, String path) { + if (value case ConvexBigInt(:final value)) return value; + throw ConvexDecodingException(path, 'expected bigint'); +} + +String _decodeString(ConvexValue value, String path) { + if (value case ConvexString(:final value)) return value; + throw ConvexDecodingException(path, 'expected string'); +} + +Uint8List _decodeBytes(ConvexValue value, String path) { + if (value case ConvexBytes(:final value)) return Uint8List.fromList(value); + throw ConvexDecodingException(path, 'expected bytes'); +} + +ConvexArray _decodeArray(ConvexValue value, String path) { + if (value is ConvexArray) return value; + throw ConvexDecodingException(path, 'expected array'); +} + +ConvexObject _decodeObject(ConvexValue value, String path) { + if (value is ConvexObject) return value; + throw ConvexDecodingException(path, 'expected object'); +} + +T _expectLiteral(T value, T expected, String path) { + if (value == expected) return value; + throw ConvexDecodingException(path, 'expected literal $expected'); +} + +T _decodePayload(T Function() decode, String path) { + try { + return decode(); + } on FormatException catch (error) { + throw ConvexDecodingException(path, error.message); + } +} + +ConvexValue _encodePayload(ConvexValue Function() encode, String path) { + try { + return encode(); + } on FormatException catch (error) { + throw ConvexEncodingException(path, error.message); + } +} + +ConvexValue _decodeRaw( + ConvexValue value, + String path, + bool Function(ConvexValue) accepts, +) { + if (accepts(value)) return value; + throw ConvexDecodingException(path, 'value does not satisfy closed union'); +} + +enum ElementsListForPageResultItemElementType { + ability('ability'), + agent('agent'), + drawing('drawing'), + image('image'), + text('text'), + utility('utility'); + + const ElementsListForPageResultItemElementType(this.wireName); + final String wireName; + + static ElementsListForPageResultItemElementType fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown ElementsListForPageResultItemElementType $wireName', + ); + } +} + +enum FoldersListTreeArgsScope { + all('all'), + owned('owned'), + shared('shared'); + + const FoldersListTreeArgsScope(this.wireName); + final String wireName; + + static FoldersListTreeArgsScope fromWireName(String wireName, String path) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown FoldersListTreeArgsScope $wireName', + ); + } +} + +enum FoldersListTreeResultItemRole { + editor('editor'), + owner('owner'), + viewer('viewer'); + + const FoldersListTreeResultItemRole(this.wireName); + final String wireName; + + static FoldersListTreeResultItemRole fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown FoldersListTreeResultItemRole $wireName', + ); + } +} + +enum HealthPingResult { + ok('ok'); + + const HealthPingResult(this.wireName); + final String wireName; + + static HealthPingResult fromWireName(String wireName, String path) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException(path, 'unknown HealthPingResult $wireName'); + } +} + +enum ImagesCompleteUploadArgsProvider { + convex('convex'), + r2('r2'); + + const ImagesCompleteUploadArgsProvider(this.wireName); + final String wireName; + + static ImagesCompleteUploadArgsProvider fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown ImagesCompleteUploadArgsProvider $wireName', + ); + } +} + +enum ImagesGenerateUploadUrlResultProvider { + r2('r2'); + + const ImagesGenerateUploadUrlResultProvider(this.wireName); + final String wireName; + + static ImagesGenerateUploadUrlResultProvider fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown ImagesGenerateUploadUrlResultProvider $wireName', + ); + } +} + +enum ImagesListForStrategyResultItemUploadStatus { + active('active'), + deleted('deleted'), + failed('failed'), + pending('pending'); + + const ImagesListForStrategyResultItemUploadStatus(this.wireName); + final String wireName; + + static ImagesListForStrategyResultItemUploadStatus fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown ImagesListForStrategyResultItemUploadStatus $wireName', + ); + } +} + +enum InvitesCreateArgsRole { + editor('editor'), + viewer('viewer'); + + const InvitesCreateArgsRole(this.wireName); + final String wireName; + + static InvitesCreateArgsRole fromWireName(String wireName, String path) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown InvitesCreateArgsRole $wireName', + ); + } +} + +enum OpsApplyBatchResultResultsItemRejectedReason { + alreadyExists('already_exists'), + elementStrategyMismatch('element_strategy_mismatch'), + lineupStrategyMismatch('lineup_strategy_mismatch'), + missingExpectedRevision('missing_expected_revision'), + notFound('not_found'), + pageStrategyMismatch('page_strategy_mismatch'), + revisionMismatch('revision_mismatch'); + + const OpsApplyBatchResultResultsItemRejectedReason(this.wireName); + final String wireName; + + static OpsApplyBatchResultResultsItemRejectedReason fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown OpsApplyBatchResultResultsItemRejectedReason $wireName', + ); + } +} + +enum SharesCreateArgsTargetType { + folder('folder'), + strategy('strategy'); + + const SharesCreateArgsTargetType(this.wireName); + final String wireName; + + static SharesCreateArgsTargetType fromWireName(String wireName, String path) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown SharesCreateArgsTargetType $wireName', + ); + } +} + +enum StrategiesListForFolderResultItemAttackLabel { + attack('Attack'), + defend('Defend'), + mixed('Mixed'), + unknown('Unknown'); + + const StrategiesListForFolderResultItemAttackLabel(this.wireName); + final String wireName; + + static StrategiesListForFolderResultItemAttackLabel fromWireName( + String wireName, + String path, + ) { + for (final value in values) { + if (value.wireName == wireName) return value; + } + throw ConvexDecodingException( + path, + 'unknown StrategiesListForFolderResultItemAttackLabel $wireName', + ); + } +} + +sealed class ImagesCompleteUploadResult { + const ImagesCompleteUploadResult(); + + factory ImagesCompleteUploadResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + final discriminator = _decodeString( + object.value['provider'] ?? _missing(path, 'provider'), + '$path.provider', + ); + return switch (discriminator) { + 'convex' => ImagesCompleteUploadResultConvex.decode(value, path), + 'r2' => ImagesCompleteUploadResultR2.decode(value, path), + _ => throw ConvexDecodingException( + path, + 'unknown discriminator $discriminator', + ), + }; + } + + ConvexObject encode(String path); +} + +sealed class OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItem(); + + factory OpsApplyBatchArgsOpsItem.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + final discriminator = _decodeString( + object.value['type'] ?? _missing(path, 'type'), + '$path.type', + ); + return switch (discriminator) { + 'strategy.patch' => OpsApplyBatchArgsOpsItemStrategyPatch.decode( + value, + path, + ), + 'page.add' => OpsApplyBatchArgsOpsItemPageAdd.decode(value, path), + 'page.patch' => OpsApplyBatchArgsOpsItemPagePatch.decode(value, path), + 'page.delete' => OpsApplyBatchArgsOpsItemPageDelete.decode(value, path), + 'page.reorder' => OpsApplyBatchArgsOpsItemPageReorder.decode(value, path), + 'pageContent.patch' => OpsApplyBatchArgsOpsItemPageContentPatch.decode( + value, + path, + ), + 'element.add' => OpsApplyBatchArgsOpsItemElementAdd.decode(value, path), + 'element.patch' => OpsApplyBatchArgsOpsItemElementPatch.decode( + value, + path, + ), + 'element.delete' => OpsApplyBatchArgsOpsItemElementDelete.decode( + value, + path, + ), + 'element.reorder' => OpsApplyBatchArgsOpsItemElementReorder.decode( + value, + path, + ), + 'lineup.add' => OpsApplyBatchArgsOpsItemLineupAdd.decode(value, path), + 'lineup.patch' => OpsApplyBatchArgsOpsItemLineupPatch.decode(value, path), + 'lineup.delete' => OpsApplyBatchArgsOpsItemLineupDelete.decode( + value, + path, + ), + 'lineup.reorder' => OpsApplyBatchArgsOpsItemLineupReorder.decode( + value, + path, + ), + _ => throw ConvexDecodingException( + path, + 'unknown discriminator $discriminator', + ), + }; + } + + ConvexObject encode(String path); +} + +sealed class OpsApplyBatchResultResultsItem { + const OpsApplyBatchResultResultsItem(); + + factory OpsApplyBatchResultResultsItem.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + final discriminator = _decodeString( + object.value['status'] ?? _missing(path, 'status'), + '$path.status', + ); + return switch (discriminator) { + 'applied' => OpsApplyBatchResultResultsItemApplied.decode(value, path), + 'noop' => OpsApplyBatchResultResultsItemNoop.decode(value, path), + 'rejected' => OpsApplyBatchResultResultsItemRejected.decode(value, path), + 'failed' => OpsApplyBatchResultResultsItemFailed.decode(value, path), + _ => throw ConvexDecodingException( + path, + 'unknown discriminator $discriminator', + ), + }; + } + + ConvexObject encode(String path); +} + +sealed class OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrent(); + + factory OpsApplyBatchResultResultsItemRejectedCurrent.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + final discriminator = _decodeString( + object.value['type'] ?? _missing(path, 'type'), + '$path.type', + ); + return switch (discriminator) { + 'strategy' => + OpsApplyBatchResultResultsItemRejectedCurrentStrategy.decode( + value, + path, + ), + 'page' => OpsApplyBatchResultResultsItemRejectedCurrentPage.decode( + value, + path, + ), + 'pageContent' => + OpsApplyBatchResultResultsItemRejectedCurrentPageContent.decode( + value, + path, + ), + 'element' => OpsApplyBatchResultResultsItemRejectedCurrentElement.decode( + value, + path, + ), + 'lineup' => OpsApplyBatchResultResultsItemRejectedCurrentLineup.decode( + value, + path, + ), + _ => throw ConvexDecodingException( + path, + 'unknown discriminator $discriminator', + ), + }; + } + + ConvexObject encode(String path); +} + +sealed class SharesRedeemResult { + const SharesRedeemResult(); + + factory SharesRedeemResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + final discriminator = _decodeString( + object.value['targetType'] ?? _missing(path, 'targetType'), + '$path.targetType', + ); + return switch (discriminator) { + 'strategy' => SharesRedeemResultStrategy.decode(value, path), + 'folder' => SharesRedeemResultFolder.decode(value, path), + _ => throw ConvexDecodingException( + path, + 'unknown discriminator $discriminator', + ), + }; + } + + ConvexObject encode(String path); +} + +final class ElementsListForPageResultItem { + const ElementsListForPageResultItem({ + required this.createdAt, + required this.deleted, + required this.elementType, + required this.pagePublicId, + required this.payload, + required this.publicId, + required this.revision, + required this.sortIndex, + required this.strategyPublicId, + required this.updatedAt, + }); + final double createdAt; + final bool deleted; + final ElementsListForPageResultItemElementType elementType; + final String pagePublicId; + final CloudPayload payload; + final String publicId; + final double revision; + final double sortIndex; + final String strategyPublicId; + final double updatedAt; + + factory ElementsListForPageResultItem.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'deleted', + 'elementType', + 'pagePublicId', + 'payload', + 'publicId', + 'revision', + 'sortIndex', + 'strategyPublicId', + 'updatedAt', + }); + return ElementsListForPageResultItem( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + deleted: _decodeBoolean( + object.value['deleted'] ?? _missing(path, 'deleted'), + '$path.deleted', + ), + elementType: ElementsListForPageResultItemElementType.fromWireName( + _decodeString( + object.value['elementType'] ?? _missing(path, 'elementType'), + '$path.elementType', + ), + '$path.elementType', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: _decodeElementsListForPageResultItemPayload( + object.value['payload'] ?? _missing(path, 'payload'), + '$path.payload', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'deleted': ConvexBoolean(deleted), + 'elementType': ConvexString(elementType.wireName), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': _encodeElementsListForPageResultItemPayload( + payload, + '$path.payload', + ), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + 'strategyPublicId': ConvexString(strategyPublicId), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class FoldersDeleteResult { + const FoldersDeleteResult({required this.ok}); + final bool ok; + + factory FoldersDeleteResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'ok'}); + return FoldersDeleteResult( + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + }); + } +} + +final class FoldersListTreeResultItem { + const FoldersListTreeResultItem({ + required this.color, + required this.createdAt, + required this.customColorValue, + required this.iconCodePoint, + required this.iconFontFamily, + required this.iconFontPackage, + required this.iconId, + required this.name, + required this.parentFolderPublicId, + required this.publicId, + required this.role, + required this.updatedAt, + }); + final String? color; + final double createdAt; + final double? customColorValue; + final double? iconCodePoint; + final String? iconFontFamily; + final String? iconFontPackage; + final double? iconId; + final String name; + final String? parentFolderPublicId; + final String publicId; + final FoldersListTreeResultItemRole role; + final double updatedAt; + + factory FoldersListTreeResultItem.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'color', + 'createdAt', + 'customColorValue', + 'iconCodePoint', + 'iconFontFamily', + 'iconFontPackage', + 'iconId', + 'name', + 'parentFolderPublicId', + 'publicId', + 'role', + 'updatedAt', + }); + return FoldersListTreeResultItem( + color: (object.value['color'] ?? _missing(path, 'color')) is ConvexNull + ? null + : _decodeString( + object.value['color'] ?? _missing(path, 'color'), + '$path.color', + ), + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + customColorValue: + (object.value['customColorValue'] ?? + _missing(path, 'customColorValue')) + is ConvexNull + ? null + : _decodeNumber( + object.value['customColorValue'] ?? + _missing(path, 'customColorValue'), + '$path.customColorValue', + ), + iconCodePoint: + (object.value['iconCodePoint'] ?? _missing(path, 'iconCodePoint')) + is ConvexNull + ? null + : _decodeNumber( + object.value['iconCodePoint'] ?? _missing(path, 'iconCodePoint'), + '$path.iconCodePoint', + ), + iconFontFamily: + (object.value['iconFontFamily'] ?? _missing(path, 'iconFontFamily')) + is ConvexNull + ? null + : _decodeString( + object.value['iconFontFamily'] ?? + _missing(path, 'iconFontFamily'), + '$path.iconFontFamily', + ), + iconFontPackage: + (object.value['iconFontPackage'] ?? _missing(path, 'iconFontPackage')) + is ConvexNull + ? null + : _decodeString( + object.value['iconFontPackage'] ?? + _missing(path, 'iconFontPackage'), + '$path.iconFontPackage', + ), + iconId: (object.value['iconId'] ?? _missing(path, 'iconId')) is ConvexNull + ? null + : _decodeNumber( + object.value['iconId'] ?? _missing(path, 'iconId'), + '$path.iconId', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + parentFolderPublicId: + (object.value['parentFolderPublicId'] ?? + _missing(path, 'parentFolderPublicId')) + is ConvexNull + ? null + : _decodeString( + object.value['parentFolderPublicId'] ?? + _missing(path, 'parentFolderPublicId'), + '$path.parentFolderPublicId', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'color': color == null ? const ConvexNull() : ConvexString(color!), + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'customColorValue': customColorValue == null + ? const ConvexNull() + : _encodeNumber(customColorValue!, '$path.customColorValue'), + 'iconCodePoint': iconCodePoint == null + ? const ConvexNull() + : _encodeNumber(iconCodePoint!, '$path.iconCodePoint'), + 'iconFontFamily': iconFontFamily == null + ? const ConvexNull() + : ConvexString(iconFontFamily!), + 'iconFontPackage': iconFontPackage == null + ? const ConvexNull() + : ConvexString(iconFontPackage!), + 'iconId': iconId == null + ? const ConvexNull() + : _encodeNumber(iconId!, '$path.iconId'), + 'name': ConvexString(name), + 'parentFolderPublicId': parentFolderPublicId == null + ? const ConvexNull() + : ConvexString(parentFolderPublicId!), + 'publicId': ConvexString(publicId), + 'role': ConvexString(role.wireName), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class ImagesCompleteUploadResultConvex + extends ImagesCompleteUploadResult { + const ImagesCompleteUploadResultConvex({required this.ok}); + final bool ok; + + factory ImagesCompleteUploadResultConvex.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'provider', 'ok'}); + return ImagesCompleteUploadResultConvex( + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'provider': ConvexString('convex'), + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + }); + } +} + +final class ImagesCompleteUploadResultR2 extends ImagesCompleteUploadResult { + const ImagesCompleteUploadResultR2({required this.ok, required this.url}); + final bool ok; + final String url; + + factory ImagesCompleteUploadResultR2.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'provider', 'ok', 'url'}); + return ImagesCompleteUploadResultR2( + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + url: _decodeString( + object.value['url'] ?? _missing(path, 'url'), + '$path.url', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'provider': ConvexString('r2'), + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + 'url': ConvexString(url), + }); + } +} + +final class ImagesGenerateUploadUrlResult { + const ImagesGenerateUploadUrlResult({ + required this.expiresAt, + required this.maxBytes, + required this.objectKey, + required this.provider, + required this.requiredHeaders, + required this.uploadId, + required this.uploadUrl, + }); + final double expiresAt; + final double maxBytes; + final String objectKey; + final ImagesGenerateUploadUrlResultProvider provider; + final Map requiredHeaders; + final String uploadId; + final String uploadUrl; + + factory ImagesGenerateUploadUrlResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'expiresAt', + 'maxBytes', + 'objectKey', + 'provider', + 'requiredHeaders', + 'uploadId', + 'uploadUrl', + }); + return ImagesGenerateUploadUrlResult( + expiresAt: _decodeNumber( + object.value['expiresAt'] ?? _missing(path, 'expiresAt'), + '$path.expiresAt', + ), + maxBytes: _decodeNumber( + object.value['maxBytes'] ?? _missing(path, 'maxBytes'), + '$path.maxBytes', + ), + objectKey: _decodeString( + object.value['objectKey'] ?? _missing(path, 'objectKey'), + '$path.objectKey', + ), + provider: ImagesGenerateUploadUrlResultProvider.fromWireName( + _decodeString( + object.value['provider'] ?? _missing(path, 'provider'), + '$path.provider', + ), + '$path.provider', + ), + requiredHeaders: Map.unmodifiable( + _decodeObject( + object.value['requiredHeaders'] ?? _missing(path, 'requiredHeaders'), + '$path.requiredHeaders', + ).value.map( + (key, item) => MapEntry( + key, + _decodeString(item, _fieldPath('$path.requiredHeaders', key)), + ), + ), + ), + uploadId: _decodeString( + object.value['uploadId'] ?? _missing(path, 'uploadId'), + '$path.uploadId', + ), + uploadUrl: _decodeString( + object.value['uploadUrl'] ?? _missing(path, 'uploadUrl'), + '$path.uploadUrl', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'expiresAt': _encodeNumber(expiresAt, '$path.expiresAt'), + 'maxBytes': _encodeNumber(maxBytes, '$path.maxBytes'), + 'objectKey': ConvexString(objectKey), + 'provider': ConvexString(provider.wireName), + 'requiredHeaders': ConvexObject( + requiredHeaders.map((key, item) => MapEntry(key, ConvexString(item))), + ), + 'uploadId': ConvexString(uploadId), + 'uploadUrl': ConvexString(uploadUrl), + }); + } +} + +final class ImagesGetAssetUrlResult { + const ImagesGetAssetUrlResult({required this.url}); + final String? url; + + factory ImagesGetAssetUrlResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'url'}); + return ImagesGetAssetUrlResult( + url: (object.value['url'] ?? _missing(path, 'url')) is ConvexNull + ? null + : _decodeString( + object.value['url'] ?? _missing(path, 'url'), + '$path.url', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'url': url == null ? const ConvexNull() : ConvexString(url!), + }); + } +} + +final class ImagesListForStrategyResultItem { + const ImagesListForStrategyResultItem({ + required this.byteSize, + required this.fileExtension, + required this.height, + required this.legacyStoragePath, + required this.mimeType, + required this.provider, + required this.publicId, + required this.uploadedAt, + required this.uploadStatus, + required this.url, + required this.width, + }); + final double? byteSize; + final String fileExtension; + final double? height; + final String? legacyStoragePath; + final String? mimeType; + final ImagesCompleteUploadArgsProvider provider; + final String publicId; + final double? uploadedAt; + final ImagesListForStrategyResultItemUploadStatus uploadStatus; + final String? url; + final double? width; + + factory ImagesListForStrategyResultItem.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'byteSize', + 'fileExtension', + 'height', + 'legacyStoragePath', + 'mimeType', + 'provider', + 'publicId', + 'uploadedAt', + 'uploadStatus', + 'url', + 'width', + }); + return ImagesListForStrategyResultItem( + byteSize: + (object.value['byteSize'] ?? _missing(path, 'byteSize')) is ConvexNull + ? null + : _decodeNumber( + object.value['byteSize'] ?? _missing(path, 'byteSize'), + '$path.byteSize', + ), + fileExtension: _decodeString( + object.value['fileExtension'] ?? _missing(path, 'fileExtension'), + '$path.fileExtension', + ), + height: (object.value['height'] ?? _missing(path, 'height')) is ConvexNull + ? null + : _decodeNumber( + object.value['height'] ?? _missing(path, 'height'), + '$path.height', + ), + legacyStoragePath: + (object.value['legacyStoragePath'] ?? + _missing(path, 'legacyStoragePath')) + is ConvexNull + ? null + : _decodeString( + object.value['legacyStoragePath'] ?? + _missing(path, 'legacyStoragePath'), + '$path.legacyStoragePath', + ), + mimeType: + (object.value['mimeType'] ?? _missing(path, 'mimeType')) is ConvexNull + ? null + : _decodeString( + object.value['mimeType'] ?? _missing(path, 'mimeType'), + '$path.mimeType', + ), + provider: ImagesCompleteUploadArgsProvider.fromWireName( + _decodeString( + object.value['provider'] ?? _missing(path, 'provider'), + '$path.provider', + ), + '$path.provider', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + uploadedAt: + (object.value['uploadedAt'] ?? _missing(path, 'uploadedAt')) + is ConvexNull + ? null + : _decodeNumber( + object.value['uploadedAt'] ?? _missing(path, 'uploadedAt'), + '$path.uploadedAt', + ), + uploadStatus: ImagesListForStrategyResultItemUploadStatus.fromWireName( + _decodeString( + object.value['uploadStatus'] ?? _missing(path, 'uploadStatus'), + '$path.uploadStatus', + ), + '$path.uploadStatus', + ), + url: (object.value['url'] ?? _missing(path, 'url')) is ConvexNull + ? null + : _decodeString( + object.value['url'] ?? _missing(path, 'url'), + '$path.url', + ), + width: (object.value['width'] ?? _missing(path, 'width')) is ConvexNull + ? null + : _decodeNumber( + object.value['width'] ?? _missing(path, 'width'), + '$path.width', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'byteSize': byteSize == null + ? const ConvexNull() + : _encodeNumber(byteSize!, '$path.byteSize'), + 'fileExtension': ConvexString(fileExtension), + 'height': height == null + ? const ConvexNull() + : _encodeNumber(height!, '$path.height'), + 'legacyStoragePath': legacyStoragePath == null + ? const ConvexNull() + : ConvexString(legacyStoragePath!), + 'mimeType': mimeType == null + ? const ConvexNull() + : ConvexString(mimeType!), + 'provider': ConvexString(provider.wireName), + 'publicId': ConvexString(publicId), + 'uploadedAt': uploadedAt == null + ? const ConvexNull() + : _encodeNumber(uploadedAt!, '$path.uploadedAt'), + 'uploadStatus': ConvexString(uploadStatus.wireName), + 'url': url == null ? const ConvexNull() : ConvexString(url!), + 'width': width == null + ? const ConvexNull() + : _encodeNumber(width!, '$path.width'), + }); + } +} + +final class InvitesRedeemResult { + const InvitesRedeemResult({ + required this.ok, + required this.role, + required this.strategyPublicId, + }); + final bool ok; + final FoldersListTreeResultItemRole role; + final String strategyPublicId; + + factory InvitesRedeemResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'ok', 'role', 'strategyPublicId'}); + return InvitesRedeemResult( + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + 'role': ConvexString(role.wireName), + 'strategyPublicId': ConvexString(strategyPublicId), + }); + } +} + +final class LineupsListForPageResultItem { + const LineupsListForPageResultItem({ + required this.createdAt, + required this.deleted, + required this.pagePublicId, + required this.payload, + required this.publicId, + required this.revision, + required this.sortIndex, + required this.strategyPublicId, + required this.updatedAt, + }); + final double createdAt; + final bool deleted; + final String pagePublicId; + final CloudPayload payload; + final String publicId; + final double revision; + final double sortIndex; + final String strategyPublicId; + final double updatedAt; + + factory LineupsListForPageResultItem.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'deleted', + 'pagePublicId', + 'payload', + 'publicId', + 'revision', + 'sortIndex', + 'strategyPublicId', + 'updatedAt', + }); + return LineupsListForPageResultItem( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + deleted: _decodeBoolean( + object.value['deleted'] ?? _missing(path, 'deleted'), + '$path.deleted', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: _decodePayload( + () => const LineupGroupConvexCodec().decode( + object.value['payload'] ?? _missing(path, 'payload'), + ), + '$path.payload', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'deleted': ConvexBoolean(deleted), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': _encodePayload( + () => const LineupGroupConvexCodec().encode(payload), + '$path.payload', + ), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + 'strategyPublicId': ConvexString(strategyPublicId), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemElementAdd + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemElementAdd({ + required this.elementPublicId, + required this.opId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + this.expectedElementRevision = const ConvexOptional.absent(), + }); + final String elementPublicId; + final ConvexOptional expectedElementRevision; + final String opId; + final String pagePublicId; + final CloudPayload payload; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemElementAdd.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'elementPublicId', + 'expectedElementRevision', + 'opId', + 'pagePublicId', + 'payload', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemElementAdd( + elementPublicId: _decodeString( + object.value['elementPublicId'] ?? _missing(path, 'elementPublicId'), + '$path.elementPublicId', + ), + expectedElementRevision: + object.value.containsKey('expectedElementRevision') + ? ConvexOptional.present( + _decodeNumber( + object.value['expectedElementRevision']!, + '$path.expectedElementRevision', + ), + ) + : const ConvexOptional.absent(), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: _decodeOpsApplyBatchArgsOpsItemElementAddPayload( + object.value['payload'] ?? _missing(path, 'payload'), + '$path.payload', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('element.add'), + 'elementPublicId': ConvexString(elementPublicId), + if (expectedElementRevision.isPresent) + 'expectedElementRevision': _encodeNumber( + expectedElementRevision.value, + '$path.expectedElementRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': _encodeOpsApplyBatchArgsOpsItemElementAddPayload( + payload, + '$path.payload', + ), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemElementDelete + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemElementDelete({ + required this.elementPublicId, + required this.expectedElementRevision, + required this.opId, + required this.pagePublicId, + }); + final String elementPublicId; + final double expectedElementRevision; + final String opId; + final String pagePublicId; + + factory OpsApplyBatchArgsOpsItemElementDelete.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'elementPublicId', + 'expectedElementRevision', + 'opId', + 'pagePublicId', + }); + return OpsApplyBatchArgsOpsItemElementDelete( + elementPublicId: _decodeString( + object.value['elementPublicId'] ?? _missing(path, 'elementPublicId'), + '$path.elementPublicId', + ), + expectedElementRevision: _decodeNumber( + object.value['expectedElementRevision'] ?? + _missing(path, 'expectedElementRevision'), + '$path.expectedElementRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('element.delete'), + 'elementPublicId': ConvexString(elementPublicId), + 'expectedElementRevision': _encodeNumber( + expectedElementRevision, + '$path.expectedElementRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + }); + } +} + +final class OpsApplyBatchArgsOpsItemElementPatch + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemElementPatch({ + required this.elementPublicId, + required this.expectedElementRevision, + required this.opId, + this.pagePublicId = const ConvexOptional.absent(), + this.payload = const ConvexOptional.absent(), + this.sortIndex = const ConvexOptional.absent(), + }); + final String elementPublicId; + final double expectedElementRevision; + final String opId; + final ConvexOptional pagePublicId; + final ConvexOptional payload; + final ConvexOptional sortIndex; + + factory OpsApplyBatchArgsOpsItemElementPatch.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'elementPublicId', + 'expectedElementRevision', + 'opId', + 'pagePublicId', + 'payload', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemElementPatch( + elementPublicId: _decodeString( + object.value['elementPublicId'] ?? _missing(path, 'elementPublicId'), + '$path.elementPublicId', + ), + expectedElementRevision: _decodeNumber( + object.value['expectedElementRevision'] ?? + _missing(path, 'expectedElementRevision'), + '$path.expectedElementRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: object.value.containsKey('pagePublicId') + ? ConvexOptional.present( + _decodeString( + object.value['pagePublicId']!, + '$path.pagePublicId', + ), + ) + : const ConvexOptional.absent(), + payload: object.value.containsKey('payload') + ? ConvexOptional.present( + _decodeOpsApplyBatchArgsOpsItemElementPatchPayload( + object.value['payload']!, + '$path.payload', + ), + ) + : const ConvexOptional.absent(), + sortIndex: object.value.containsKey('sortIndex') + ? ConvexOptional.present( + _decodeNumber(object.value['sortIndex']!, '$path.sortIndex'), + ) + : const ConvexOptional.absent(), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('element.patch'), + 'elementPublicId': ConvexString(elementPublicId), + 'expectedElementRevision': _encodeNumber( + expectedElementRevision, + '$path.expectedElementRevision', + ), + 'opId': ConvexString(opId), + if (pagePublicId.isPresent) + 'pagePublicId': ConvexString(pagePublicId.value), + if (payload.isPresent) + 'payload': _encodeOpsApplyBatchArgsOpsItemElementPatchPayload( + payload.value, + '$path.payload', + ), + if (sortIndex.isPresent) + 'sortIndex': _encodeNumber(sortIndex.value, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemElementReorder + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemElementReorder({ + required this.elementPublicId, + required this.expectedElementRevision, + required this.opId, + required this.pagePublicId, + required this.sortIndex, + }); + final String elementPublicId; + final double expectedElementRevision; + final String opId; + final String pagePublicId; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemElementReorder.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'elementPublicId', + 'expectedElementRevision', + 'opId', + 'pagePublicId', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemElementReorder( + elementPublicId: _decodeString( + object.value['elementPublicId'] ?? _missing(path, 'elementPublicId'), + '$path.elementPublicId', + ), + expectedElementRevision: _decodeNumber( + object.value['expectedElementRevision'] ?? + _missing(path, 'expectedElementRevision'), + '$path.expectedElementRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('element.reorder'), + 'elementPublicId': ConvexString(elementPublicId), + 'expectedElementRevision': _encodeNumber( + expectedElementRevision, + '$path.expectedElementRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemLineupAdd extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemLineupAdd({ + required this.lineupPublicId, + required this.opId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + this.expectedLineupRevision = const ConvexOptional.absent(), + }); + final ConvexOptional expectedLineupRevision; + final String lineupPublicId; + final String opId; + final String pagePublicId; + final CloudPayload payload; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemLineupAdd.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedLineupRevision', + 'lineupPublicId', + 'opId', + 'pagePublicId', + 'payload', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemLineupAdd( + expectedLineupRevision: object.value.containsKey('expectedLineupRevision') + ? ConvexOptional.present( + _decodeNumber( + object.value['expectedLineupRevision']!, + '$path.expectedLineupRevision', + ), + ) + : const ConvexOptional.absent(), + lineupPublicId: _decodeString( + object.value['lineupPublicId'] ?? _missing(path, 'lineupPublicId'), + '$path.lineupPublicId', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: _decodePayload( + () => const LineupGroupConvexCodec().decode( + object.value['payload'] ?? _missing(path, 'payload'), + ), + '$path.payload', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('lineup.add'), + if (expectedLineupRevision.isPresent) + 'expectedLineupRevision': _encodeNumber( + expectedLineupRevision.value, + '$path.expectedLineupRevision', + ), + 'lineupPublicId': ConvexString(lineupPublicId), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': _encodePayload( + () => const LineupGroupConvexCodec().encode(payload), + '$path.payload', + ), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemLineupDelete + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemLineupDelete({ + required this.expectedLineupRevision, + required this.lineupPublicId, + required this.opId, + required this.pagePublicId, + }); + final double expectedLineupRevision; + final String lineupPublicId; + final String opId; + final String pagePublicId; + + factory OpsApplyBatchArgsOpsItemLineupDelete.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedLineupRevision', + 'lineupPublicId', + 'opId', + 'pagePublicId', + }); + return OpsApplyBatchArgsOpsItemLineupDelete( + expectedLineupRevision: _decodeNumber( + object.value['expectedLineupRevision'] ?? + _missing(path, 'expectedLineupRevision'), + '$path.expectedLineupRevision', + ), + lineupPublicId: _decodeString( + object.value['lineupPublicId'] ?? _missing(path, 'lineupPublicId'), + '$path.lineupPublicId', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('lineup.delete'), + 'expectedLineupRevision': _encodeNumber( + expectedLineupRevision, + '$path.expectedLineupRevision', + ), + 'lineupPublicId': ConvexString(lineupPublicId), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + }); + } +} + +final class OpsApplyBatchArgsOpsItemLineupPatch + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemLineupPatch({ + required this.expectedLineupRevision, + required this.lineupPublicId, + required this.opId, + this.pagePublicId = const ConvexOptional.absent(), + this.payload = const ConvexOptional.absent(), + this.sortIndex = const ConvexOptional.absent(), + }); + final double expectedLineupRevision; + final String lineupPublicId; + final String opId; + final ConvexOptional pagePublicId; + final ConvexOptional payload; + final ConvexOptional sortIndex; + + factory OpsApplyBatchArgsOpsItemLineupPatch.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedLineupRevision', + 'lineupPublicId', + 'opId', + 'pagePublicId', + 'payload', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemLineupPatch( + expectedLineupRevision: _decodeNumber( + object.value['expectedLineupRevision'] ?? + _missing(path, 'expectedLineupRevision'), + '$path.expectedLineupRevision', + ), + lineupPublicId: _decodeString( + object.value['lineupPublicId'] ?? _missing(path, 'lineupPublicId'), + '$path.lineupPublicId', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: object.value.containsKey('pagePublicId') + ? ConvexOptional.present( + _decodeString( + object.value['pagePublicId']!, + '$path.pagePublicId', + ), + ) + : const ConvexOptional.absent(), + payload: object.value.containsKey('payload') + ? ConvexOptional.present( + _decodePayload( + () => const LineupGroupConvexCodec().decode( + object.value['payload']!, + ), + '$path.payload', + ), + ) + : const ConvexOptional.absent(), + sortIndex: object.value.containsKey('sortIndex') + ? ConvexOptional.present( + _decodeNumber(object.value['sortIndex']!, '$path.sortIndex'), + ) + : const ConvexOptional.absent(), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('lineup.patch'), + 'expectedLineupRevision': _encodeNumber( + expectedLineupRevision, + '$path.expectedLineupRevision', + ), + 'lineupPublicId': ConvexString(lineupPublicId), + 'opId': ConvexString(opId), + if (pagePublicId.isPresent) + 'pagePublicId': ConvexString(pagePublicId.value), + if (payload.isPresent) + 'payload': _encodePayload( + () => const LineupGroupConvexCodec().encode(payload.value), + '$path.payload', + ), + if (sortIndex.isPresent) + 'sortIndex': _encodeNumber(sortIndex.value, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemLineupReorder + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemLineupReorder({ + required this.expectedLineupRevision, + required this.lineupPublicId, + required this.opId, + required this.pagePublicId, + required this.sortIndex, + }); + final double expectedLineupRevision; + final String lineupPublicId; + final String opId; + final String pagePublicId; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemLineupReorder.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedLineupRevision', + 'lineupPublicId', + 'opId', + 'pagePublicId', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemLineupReorder( + expectedLineupRevision: _decodeNumber( + object.value['expectedLineupRevision'] ?? + _missing(path, 'expectedLineupRevision'), + '$path.expectedLineupRevision', + ), + lineupPublicId: _decodeString( + object.value['lineupPublicId'] ?? _missing(path, 'lineupPublicId'), + '$path.lineupPublicId', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('lineup.reorder'), + 'expectedLineupRevision': _encodeNumber( + expectedLineupRevision, + '$path.expectedLineupRevision', + ), + 'lineupPublicId': ConvexString(lineupPublicId), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageAdd extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemPageAdd({ + required this.expectedStrategyRevision, + required this.opId, + required this.pagePublicId, + required this.payload, + required this.sortIndex, + }); + final double expectedStrategyRevision; + final String opId; + final String pagePublicId; + final OpsApplyBatchArgsOpsItemPageAddPayload payload; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemPageAdd.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedStrategyRevision', + 'opId', + 'pagePublicId', + 'payload', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemPageAdd( + expectedStrategyRevision: _decodeNumber( + object.value['expectedStrategyRevision'] ?? + _missing(path, 'expectedStrategyRevision'), + '$path.expectedStrategyRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: OpsApplyBatchArgsOpsItemPageAddPayload.decode( + object.value['payload'] ?? _missing(path, 'payload'), + '$path.payload', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('page.add'), + 'expectedStrategyRevision': _encodeNumber( + expectedStrategyRevision, + '$path.expectedStrategyRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': payload.encode('$path.payload'), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageAddPayload { + const OpsApplyBatchArgsOpsItemPageAddPayload({ + this.isAttack = const ConvexOptional.absent(), + this.name = const ConvexOptional.absent(), + this.settings = const ConvexOptional.absent(), + }); + final ConvexOptional isAttack; + final ConvexOptional name; + final ConvexOptional settings; + + factory OpsApplyBatchArgsOpsItemPageAddPayload.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'isAttack', 'name', 'settings'}); + return OpsApplyBatchArgsOpsItemPageAddPayload( + isAttack: object.value.containsKey('isAttack') + ? ConvexOptional.present( + _decodeBoolean(object.value['isAttack']!, '$path.isAttack'), + ) + : const ConvexOptional.absent(), + name: object.value.containsKey('name') + ? ConvexOptional.present( + _decodeString(object.value['name']!, '$path.name'), + ) + : const ConvexOptional.absent(), + settings: object.value.containsKey('settings') + ? ConvexOptional.present( + OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + object.value['settings']!, + '$path.settings', + ), + ) + : const ConvexOptional.absent(), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + if (isAttack.isPresent) 'isAttack': ConvexBoolean(isAttack.value), + if (name.isPresent) 'name': ConvexString(name.value), + if (settings.isPresent) + 'settings': settings.value.encode('$path.settings'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageAddPayloadSettings { + const OpsApplyBatchArgsOpsItemPageAddPayloadSettings({ + required this.abilitySize, + required this.agentSize, + required this.useNeutralTeamColors, + }); + final double abilitySize; + final double agentSize; + final bool useNeutralTeamColors; + + factory OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'abilitySize', + 'agentSize', + 'useNeutralTeamColors', + }); + return OpsApplyBatchArgsOpsItemPageAddPayloadSettings( + abilitySize: _decodeNumber( + object.value['abilitySize'] ?? _missing(path, 'abilitySize'), + '$path.abilitySize', + ), + agentSize: _decodeNumber( + object.value['agentSize'] ?? _missing(path, 'agentSize'), + '$path.agentSize', + ), + useNeutralTeamColors: _decodeBoolean( + object.value['useNeutralTeamColors'] ?? + _missing(path, 'useNeutralTeamColors'), + '$path.useNeutralTeamColors', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'abilitySize': _encodeNumber(abilitySize, '$path.abilitySize'), + 'agentSize': _encodeNumber(agentSize, '$path.agentSize'), + 'useNeutralTeamColors': ConvexBoolean(useNeutralTeamColors), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageContentPatch + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemPageContentPatch({ + required this.expectedPageContentRevision, + required this.opId, + required this.pagePublicId, + required this.settings, + }); + final double expectedPageContentRevision; + final String opId; + final String pagePublicId; + final OpsApplyBatchArgsOpsItemPageAddPayloadSettings settings; + + factory OpsApplyBatchArgsOpsItemPageContentPatch.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedPageContentRevision', + 'opId', + 'pagePublicId', + 'settings', + }); + return OpsApplyBatchArgsOpsItemPageContentPatch( + expectedPageContentRevision: _decodeNumber( + object.value['expectedPageContentRevision'] ?? + _missing(path, 'expectedPageContentRevision'), + '$path.expectedPageContentRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + settings: OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + object.value['settings'] ?? _missing(path, 'settings'), + '$path.settings', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('pageContent.patch'), + 'expectedPageContentRevision': _encodeNumber( + expectedPageContentRevision, + '$path.expectedPageContentRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'settings': settings.encode('$path.settings'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageDelete + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemPageDelete({ + required this.expectedStrategyRevision, + required this.opId, + required this.pagePublicId, + }); + final double expectedStrategyRevision; + final String opId; + final String pagePublicId; + + factory OpsApplyBatchArgsOpsItemPageDelete.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedStrategyRevision', + 'opId', + 'pagePublicId', + }); + return OpsApplyBatchArgsOpsItemPageDelete( + expectedStrategyRevision: _decodeNumber( + object.value['expectedStrategyRevision'] ?? + _missing(path, 'expectedStrategyRevision'), + '$path.expectedStrategyRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('page.delete'), + 'expectedStrategyRevision': _encodeNumber( + expectedStrategyRevision, + '$path.expectedStrategyRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPagePatch extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemPagePatch({ + required this.expectedPageRevision, + required this.opId, + required this.pagePublicId, + required this.payload, + }); + final double expectedPageRevision; + final String opId; + final String pagePublicId; + final OpsApplyBatchArgsOpsItemPageAddPayload payload; + + factory OpsApplyBatchArgsOpsItemPagePatch.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedPageRevision', + 'opId', + 'pagePublicId', + 'payload', + }); + return OpsApplyBatchArgsOpsItemPagePatch( + expectedPageRevision: _decodeNumber( + object.value['expectedPageRevision'] ?? + _missing(path, 'expectedPageRevision'), + '$path.expectedPageRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + payload: OpsApplyBatchArgsOpsItemPageAddPayload.decode( + object.value['payload'] ?? _missing(path, 'payload'), + '$path.payload', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('page.patch'), + 'expectedPageRevision': _encodeNumber( + expectedPageRevision, + '$path.expectedPageRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'payload': payload.encode('$path.payload'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemPageReorder + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemPageReorder({ + required this.expectedStrategyRevision, + required this.opId, + required this.pagePublicId, + required this.sortIndex, + }); + final double expectedStrategyRevision; + final String opId; + final String pagePublicId; + final double sortIndex; + + factory OpsApplyBatchArgsOpsItemPageReorder.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedStrategyRevision', + 'opId', + 'pagePublicId', + 'sortIndex', + }); + return OpsApplyBatchArgsOpsItemPageReorder( + expectedStrategyRevision: _decodeNumber( + object.value['expectedStrategyRevision'] ?? + _missing(path, 'expectedStrategyRevision'), + '$path.expectedStrategyRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + pagePublicId: _decodeString( + object.value['pagePublicId'] ?? _missing(path, 'pagePublicId'), + '$path.pagePublicId', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('page.reorder'), + 'expectedStrategyRevision': _encodeNumber( + expectedStrategyRevision, + '$path.expectedStrategyRevision', + ), + 'opId': ConvexString(opId), + 'pagePublicId': ConvexString(pagePublicId), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemStrategyPatch + extends OpsApplyBatchArgsOpsItem { + const OpsApplyBatchArgsOpsItemStrategyPatch({ + required this.expectedStrategyRevision, + required this.opId, + required this.payload, + }); + final double expectedStrategyRevision; + final String opId; + final OpsApplyBatchArgsOpsItemStrategyPatchPayload payload; + + factory OpsApplyBatchArgsOpsItemStrategyPatch.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'type', + 'expectedStrategyRevision', + 'opId', + 'payload', + }); + return OpsApplyBatchArgsOpsItemStrategyPatch( + expectedStrategyRevision: _decodeNumber( + object.value['expectedStrategyRevision'] ?? + _missing(path, 'expectedStrategyRevision'), + '$path.expectedStrategyRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + payload: OpsApplyBatchArgsOpsItemStrategyPatchPayload.decode( + object.value['payload'] ?? _missing(path, 'payload'), + '$path.payload', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('strategy.patch'), + 'expectedStrategyRevision': _encodeNumber( + expectedStrategyRevision, + '$path.expectedStrategyRevision', + ), + 'opId': ConvexString(opId), + 'payload': payload.encode('$path.payload'), + }); + } +} + +final class OpsApplyBatchArgsOpsItemStrategyPatchPayload { + const OpsApplyBatchArgsOpsItemStrategyPatchPayload({ + this.clearThemeOverridePalette = const ConvexOptional.absent(), + this.clearThemeProfileId = const ConvexOptional.absent(), + this.mapData = const ConvexOptional.absent(), + this.name = const ConvexOptional.absent(), + this.themeOverridePalette = const ConvexOptional.absent(), + this.themeProfileId = const ConvexOptional.absent(), + }); + final ConvexOptional clearThemeOverridePalette; + final ConvexOptional clearThemeProfileId; + final ConvexOptional mapData; + final ConvexOptional name; + final ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette; + final ConvexOptional themeProfileId; + + factory OpsApplyBatchArgsOpsItemStrategyPatchPayload.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'clearThemeOverridePalette', + 'clearThemeProfileId', + 'mapData', + 'name', + 'themeOverridePalette', + 'themeProfileId', + }); + return OpsApplyBatchArgsOpsItemStrategyPatchPayload( + clearThemeOverridePalette: + object.value.containsKey('clearThemeOverridePalette') + ? ConvexOptional.present( + _decodeBoolean( + object.value['clearThemeOverridePalette']!, + '$path.clearThemeOverridePalette', + ), + ) + : const ConvexOptional.absent(), + clearThemeProfileId: object.value.containsKey('clearThemeProfileId') + ? ConvexOptional.present( + _decodeBoolean( + object.value['clearThemeProfileId']!, + '$path.clearThemeProfileId', + ), + ) + : const ConvexOptional.absent(), + mapData: object.value.containsKey('mapData') + ? ConvexOptional.present( + _decodeString(object.value['mapData']!, '$path.mapData'), + ) + : const ConvexOptional.absent(), + name: object.value.containsKey('name') + ? ConvexOptional.present( + _decodeString(object.value['name']!, '$path.name'), + ) + : const ConvexOptional.absent(), + themeOverridePalette: object.value.containsKey('themeOverridePalette') + ? ConvexOptional.present( + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + object.value['themeOverridePalette']!, + '$path.themeOverridePalette', + ), + ) + : const ConvexOptional.absent(), + themeProfileId: object.value.containsKey('themeProfileId') + ? ConvexOptional.present( + _decodeString( + object.value['themeProfileId']!, + '$path.themeProfileId', + ), + ) + : const ConvexOptional.absent(), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + if (clearThemeOverridePalette.isPresent) + 'clearThemeOverridePalette': ConvexBoolean( + clearThemeOverridePalette.value, + ), + if (clearThemeProfileId.isPresent) + 'clearThemeProfileId': ConvexBoolean(clearThemeProfileId.value), + if (mapData.isPresent) 'mapData': ConvexString(mapData.value), + if (name.isPresent) 'name': ConvexString(name.value), + if (themeOverridePalette.isPresent) + 'themeOverridePalette': themeOverridePalette.value.encode( + '$path.themeOverridePalette', + ), + if (themeProfileId.isPresent) + 'themeProfileId': ConvexString(themeProfileId.value), + }); + } +} + +final class OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette { + const OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette({ + required this.baseValue, + required this.detail, + required this.highlight, + }); + final String baseValue; + final String detail; + final String highlight; + + factory OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'base', 'detail', 'highlight'}); + return OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette( + baseValue: _decodeString( + object.value['base'] ?? _missing(path, 'base'), + '$path.base', + ), + detail: _decodeString( + object.value['detail'] ?? _missing(path, 'detail'), + '$path.detail', + ), + highlight: _decodeString( + object.value['highlight'] ?? _missing(path, 'highlight'), + '$path.highlight', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'base': ConvexString(baseValue), + 'detail': ConvexString(detail), + 'highlight': ConvexString(highlight), + }); + } +} + +final class OpsApplyBatchResult { + const OpsApplyBatchResult({ + required this.results, + required this.strategyPublicId, + }); + final List results; + final String strategyPublicId; + + factory OpsApplyBatchResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'results', 'strategyPublicId'}); + return OpsApplyBatchResult( + results: + _decodeArray( + object.value['results'] ?? _missing(path, 'results'), + '$path.results', + ).value.indexed + .map( + (entry) => OpsApplyBatchResultResultsItem.decode( + entry.$2, + _indexPath('$path.results', entry.$1), + ), + ) + .toList(growable: false), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'results': ConvexArray( + results.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.results', entry.$1)), + ) + .toList(growable: false), + ), + 'strategyPublicId': ConvexString(strategyPublicId), + }); + } +} + +final class OpsApplyBatchResultResultsItemApplied + extends OpsApplyBatchResultResultsItem { + const OpsApplyBatchResultResultsItemApplied({ + required this.appliedRevision, + required this.opId, + }); + final double appliedRevision; + final String opId; + + factory OpsApplyBatchResultResultsItemApplied.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'status', + 'appliedRevision', + 'opId', + }); + return OpsApplyBatchResultResultsItemApplied( + appliedRevision: _decodeNumber( + object.value['appliedRevision'] ?? _missing(path, 'appliedRevision'), + '$path.appliedRevision', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'status': ConvexString('applied'), + 'appliedRevision': _encodeNumber( + appliedRevision, + '$path.appliedRevision', + ), + 'opId': ConvexString(opId), + }); + } +} + +final class OpsApplyBatchResultResultsItemFailed + extends OpsApplyBatchResultResultsItem { + const OpsApplyBatchResultResultsItemFailed({ + required this.code, + required this.message, + required this.opId, + required this.rawCode, + }); + final String code; + final String message; + final String opId; + final String rawCode; + + factory OpsApplyBatchResultResultsItemFailed.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'status', + 'code', + 'message', + 'opId', + 'rawCode', + }); + return OpsApplyBatchResultResultsItemFailed( + code: _decodeString( + object.value['code'] ?? _missing(path, 'code'), + '$path.code', + ), + message: _decodeString( + object.value['message'] ?? _missing(path, 'message'), + '$path.message', + ), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + rawCode: _decodeString( + object.value['rawCode'] ?? _missing(path, 'rawCode'), + '$path.rawCode', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'status': ConvexString('failed'), + 'code': ConvexString(code), + 'message': ConvexString(message), + 'opId': ConvexString(opId), + 'rawCode': ConvexString(rawCode), + }); + } +} + +final class OpsApplyBatchResultResultsItemNoop + extends OpsApplyBatchResultResultsItem { + const OpsApplyBatchResultResultsItemNoop({ + required this.opId, + this.currentRevision = const ConvexOptional.absent(), + }); + final ConvexOptional currentRevision; + final String opId; + + factory OpsApplyBatchResultResultsItemNoop.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'status', + 'currentRevision', + 'opId', + }); + return OpsApplyBatchResultResultsItemNoop( + currentRevision: object.value.containsKey('currentRevision') + ? ConvexOptional.present( + _decodeNumber( + object.value['currentRevision']!, + '$path.currentRevision', + ), + ) + : const ConvexOptional.absent(), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'status': ConvexString('noop'), + if (currentRevision.isPresent) + 'currentRevision': _encodeNumber( + currentRevision.value, + '$path.currentRevision', + ), + 'opId': ConvexString(opId), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejected + extends OpsApplyBatchResultResultsItem { + const OpsApplyBatchResultResultsItemRejected({ + required this.opId, + required this.reason, + this.current = const ConvexOptional.absent(), + }); + final ConvexOptional current; + final String opId; + final OpsApplyBatchResultResultsItemRejectedReason reason; + + factory OpsApplyBatchResultResultsItemRejected.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'status', + 'current', + 'opId', + 'reason', + }); + return OpsApplyBatchResultResultsItemRejected( + current: object.value.containsKey('current') + ? ConvexOptional.present( + OpsApplyBatchResultResultsItemRejectedCurrent.decode( + object.value['current']!, + '$path.current', + ), + ) + : const ConvexOptional.absent(), + opId: _decodeString( + object.value['opId'] ?? _missing(path, 'opId'), + '$path.opId', + ), + reason: OpsApplyBatchResultResultsItemRejectedReason.fromWireName( + _decodeString( + object.value['reason'] ?? _missing(path, 'reason'), + '$path.reason', + ), + '$path.reason', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'status': ConvexString('rejected'), + if (current.isPresent) 'current': current.value.encode('$path.current'), + 'opId': ConvexString(opId), + 'reason': ConvexString(reason.wireName), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentElement + extends OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrentElement({ + required this.revision, + required this.value, + }); + final double revision; + final CloudPayload value; + + factory OpsApplyBatchResultResultsItemRejectedCurrentElement.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'type', 'revision', 'value'}); + return OpsApplyBatchResultResultsItemRejectedCurrentElement( + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + value: _decodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( + object.value['value'] ?? _missing(path, 'value'), + '$path.value', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('element'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'value': _encodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( + value, + '$path.value', + ), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentLineup + extends OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrentLineup({ + required this.revision, + required this.value, + }); + final double revision; + final CloudPayload value; + + factory OpsApplyBatchResultResultsItemRejectedCurrentLineup.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'type', 'revision', 'value'}); + return OpsApplyBatchResultResultsItemRejectedCurrentLineup( + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + value: _decodePayload( + () => const LineupGroupConvexCodec().decode( + object.value['value'] ?? _missing(path, 'value'), + ), + '$path.value', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('lineup'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'value': _encodePayload( + () => const LineupGroupConvexCodec().encode(value), + '$path.value', + ), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentPage + extends OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrentPage({ + required this.revision, + required this.value, + }); + final double revision; + final OpsApplyBatchResultResultsItemRejectedCurrentPageValue value; + + factory OpsApplyBatchResultResultsItemRejectedCurrentPage.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'type', 'revision', 'value'}); + return OpsApplyBatchResultResultsItemRejectedCurrentPage( + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + value: OpsApplyBatchResultResultsItemRejectedCurrentPageValue.decode( + object.value['value'] ?? _missing(path, 'value'), + '$path.value', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('page'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'value': value.encode('$path.value'), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentPageContent + extends OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrentPageContent({ + required this.revision, + required this.value, + }); + final double revision; + final OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue value; + + factory OpsApplyBatchResultResultsItemRejectedCurrentPageContent.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'type', 'revision', 'value'}); + return OpsApplyBatchResultResultsItemRejectedCurrentPageContent( + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + value: + OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue.decode( + object.value['value'] ?? _missing(path, 'value'), + '$path.value', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('pageContent'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'value': value.encode('$path.value'), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue { + const OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue({ + required this.settings, + }); + final OpsApplyBatchArgsOpsItemPageAddPayloadSettings? settings; + + factory OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'settings'}); + return OpsApplyBatchResultResultsItemRejectedCurrentPageContentValue( + settings: + (object.value['settings'] ?? _missing(path, 'settings')) is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + object.value['settings'] ?? _missing(path, 'settings'), + '$path.settings', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'settings': settings == null + ? const ConvexNull() + : settings!.encode('$path.settings'), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentPageValue { + const OpsApplyBatchResultResultsItemRejectedCurrentPageValue({ + required this.isAttack, + required this.name, + required this.sortIndex, + }); + final bool isAttack; + final String name; + final double sortIndex; + + factory OpsApplyBatchResultResultsItemRejectedCurrentPageValue.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'isAttack', 'name', 'sortIndex'}); + return OpsApplyBatchResultResultsItemRejectedCurrentPageValue( + isAttack: _decodeBoolean( + object.value['isAttack'] ?? _missing(path, 'isAttack'), + '$path.isAttack', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'isAttack': ConvexBoolean(isAttack), + 'name': ConvexString(name), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentStrategy + extends OpsApplyBatchResultResultsItemRejectedCurrent { + const OpsApplyBatchResultResultsItemRejectedCurrentStrategy({ + required this.revision, + required this.value, + }); + final double revision; + final OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue value; + + factory OpsApplyBatchResultResultsItemRejectedCurrentStrategy.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'type', 'revision', 'value'}); + return OpsApplyBatchResultResultsItemRejectedCurrentStrategy( + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + value: OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue.decode( + object.value['value'] ?? _missing(path, 'value'), + '$path.value', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'type': ConvexString('strategy'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'value': value.encode('$path.value'), + }); + } +} + +final class OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue { + const OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue({ + required this.mapData, + required this.name, + required this.themeOverridePalette, + required this.themeProfileId, + }); + final String mapData; + final String name; + final OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette? + themeOverridePalette; + final String? themeProfileId; + + factory OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'mapData', + 'name', + 'themeOverridePalette', + 'themeProfileId', + }); + return OpsApplyBatchResultResultsItemRejectedCurrentStrategyValue( + mapData: _decodeString( + object.value['mapData'] ?? _missing(path, 'mapData'), + '$path.mapData', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + themeOverridePalette: + (object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette')) + is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette'), + '$path.themeOverridePalette', + ), + themeProfileId: + (object.value['themeProfileId'] ?? _missing(path, 'themeProfileId')) + is ConvexNull + ? null + : _decodeString( + object.value['themeProfileId'] ?? + _missing(path, 'themeProfileId'), + '$path.themeProfileId', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'mapData': ConvexString(mapData), + 'name': ConvexString(name), + 'themeOverridePalette': themeOverridePalette == null + ? const ConvexNull() + : themeOverridePalette!.encode('$path.themeOverridePalette'), + 'themeProfileId': themeProfileId == null + ? const ConvexNull() + : ConvexString(themeProfileId!), + }); + } +} + +final class PageGetSnapshotResult { + const PageGetSnapshotResult({ + required this.assets, + required this.content, + required this.elements, + required this.lineups, + required this.page, + }); + final List assets; + final PageGetSnapshotResultContent content; + final List elements; + final List lineups; + final PageGetSnapshotResultPage page; + + factory PageGetSnapshotResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'assets', + 'content', + 'elements', + 'lineups', + 'page', + }); + return PageGetSnapshotResult( + assets: + _decodeArray( + object.value['assets'] ?? _missing(path, 'assets'), + '$path.assets', + ).value.indexed + .map( + (entry) => ImagesListForStrategyResultItem.decode( + entry.$2, + _indexPath('$path.assets', entry.$1), + ), + ) + .toList(growable: false), + content: PageGetSnapshotResultContent.decode( + object.value['content'] ?? _missing(path, 'content'), + '$path.content', + ), + elements: + _decodeArray( + object.value['elements'] ?? _missing(path, 'elements'), + '$path.elements', + ).value.indexed + .map( + (entry) => ElementsListForPageResultItem.decode( + entry.$2, + _indexPath('$path.elements', entry.$1), + ), + ) + .toList(growable: false), + lineups: + _decodeArray( + object.value['lineups'] ?? _missing(path, 'lineups'), + '$path.lineups', + ).value.indexed + .map( + (entry) => LineupsListForPageResultItem.decode( + entry.$2, + _indexPath('$path.lineups', entry.$1), + ), + ) + .toList(growable: false), + page: PageGetSnapshotResultPage.decode( + object.value['page'] ?? _missing(path, 'page'), + '$path.page', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'assets': ConvexArray( + assets.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.assets', entry.$1)), + ) + .toList(growable: false), + ), + 'content': content.encode('$path.content'), + 'elements': ConvexArray( + elements.indexed + .map( + (entry) => + entry.$2.encode(_indexPath('$path.elements', entry.$1)), + ) + .toList(growable: false), + ), + 'lineups': ConvexArray( + lineups.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.lineups', entry.$1)), + ) + .toList(growable: false), + ), + 'page': page.encode('$path.page'), + }); + } +} + +final class PageGetSnapshotResultContent { + const PageGetSnapshotResultContent({ + required this.createdAt, + required this.revision, + required this.settings, + required this.updatedAt, + }); + final double createdAt; + final double revision; + final OpsApplyBatchArgsOpsItemPageAddPayloadSettings? settings; + final double updatedAt; + + factory PageGetSnapshotResultContent.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'revision', + 'settings', + 'updatedAt', + }); + return PageGetSnapshotResultContent( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + settings: + (object.value['settings'] ?? _missing(path, 'settings')) is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + object.value['settings'] ?? _missing(path, 'settings'), + '$path.settings', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'revision': _encodeNumber(revision, '$path.revision'), + 'settings': settings == null + ? const ConvexNull() + : settings!.encode('$path.settings'), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class PageGetSnapshotResultPage { + const PageGetSnapshotResultPage({ + required this.createdAt, + required this.isAttack, + required this.name, + required this.publicId, + required this.revision, + required this.sortIndex, + required this.strategyPublicId, + required this.updatedAt, + }); + final double createdAt; + final bool isAttack; + final String name; + final String publicId; + final double revision; + final double sortIndex; + final String strategyPublicId; + final double updatedAt; + + factory PageGetSnapshotResultPage.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'isAttack', + 'name', + 'publicId', + 'revision', + 'sortIndex', + 'strategyPublicId', + 'updatedAt', + }); + return PageGetSnapshotResultPage( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + isAttack: _decodeBoolean( + object.value['isAttack'] ?? _missing(path, 'isAttack'), + '$path.isAttack', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'isAttack': ConvexBoolean(isAttack), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + 'strategyPublicId': ConvexString(strategyPublicId), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class SharesListResultItem { + const SharesListResultItem({ + required this.createdAt, + required this.revokedAt, + required this.role, + required this.token, + }); + final double createdAt; + final double? revokedAt; + final InvitesCreateArgsRole role; + final String token; + + factory SharesListResultItem.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'revokedAt', + 'role', + 'token', + }); + return SharesListResultItem( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + revokedAt: + (object.value['revokedAt'] ?? _missing(path, 'revokedAt')) + is ConvexNull + ? null + : _decodeNumber( + object.value['revokedAt'] ?? _missing(path, 'revokedAt'), + '$path.revokedAt', + ), + role: InvitesCreateArgsRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + token: _decodeString( + object.value['token'] ?? _missing(path, 'token'), + '$path.token', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'revokedAt': revokedAt == null + ? const ConvexNull() + : _encodeNumber(revokedAt!, '$path.revokedAt'), + 'role': ConvexString(role.wireName), + 'token': ConvexString(token), + }); + } +} + +final class SharesRedeemResultFolder extends SharesRedeemResult { + const SharesRedeemResultFolder({ + required this.folderPublicId, + required this.ok, + required this.role, + }); + final String folderPublicId; + final bool ok; + final FoldersListTreeResultItemRole role; + + factory SharesRedeemResultFolder.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'targetType', + 'folderPublicId', + 'ok', + 'role', + }); + return SharesRedeemResultFolder( + folderPublicId: _decodeString( + object.value['folderPublicId'] ?? _missing(path, 'folderPublicId'), + '$path.folderPublicId', + ), + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'targetType': ConvexString('folder'), + 'folderPublicId': ConvexString(folderPublicId), + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + 'role': ConvexString(role.wireName), + }); + } +} + +final class SharesRedeemResultStrategy extends SharesRedeemResult { + const SharesRedeemResultStrategy({ + required this.folderPublicId, + required this.ok, + required this.role, + required this.strategyPublicId, + }); + final String? folderPublicId; + final bool ok; + final FoldersListTreeResultItemRole role; + final String strategyPublicId; + + factory SharesRedeemResultStrategy.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'targetType', + 'folderPublicId', + 'ok', + 'role', + 'strategyPublicId', + }); + return SharesRedeemResultStrategy( + folderPublicId: + (object.value['folderPublicId'] ?? _missing(path, 'folderPublicId')) + is ConvexNull + ? null + : _decodeString( + object.value['folderPublicId'] ?? + _missing(path, 'folderPublicId'), + '$path.folderPublicId', + ), + ok: _expectLiteral( + _decodeBoolean(object.value['ok'] ?? _missing(path, 'ok'), '$path.ok'), + true, + '$path.ok', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + ); + } + + @override + ConvexObject encode(String path) { + return ConvexObject({ + 'targetType': ConvexString('strategy'), + 'folderPublicId': folderPublicId == null + ? const ConvexNull() + : ConvexString(folderPublicId!), + 'ok': ConvexBoolean(_expectLiteral(ok, true, '$path.ok')), + 'role': ConvexString(role.wireName), + 'strategyPublicId': ConvexString(strategyPublicId), + }); + } +} + +final class StrategiesGetHeaderResult { + const StrategiesGetHeaderResult({ + required this.createdAt, + required this.mapData, + required this.name, + required this.publicId, + required this.revision, + required this.role, + required this.themeOverridePalette, + required this.themeProfileId, + required this.updatedAt, + }); + final double createdAt; + final String mapData; + final String name; + final String publicId; + final double revision; + final FoldersListTreeResultItemRole role; + final OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette? + themeOverridePalette; + final String? themeProfileId; + final double updatedAt; + + factory StrategiesGetHeaderResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'createdAt', + 'mapData', + 'name', + 'publicId', + 'revision', + 'role', + 'themeOverridePalette', + 'themeProfileId', + 'updatedAt', + }); + return StrategiesGetHeaderResult( + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + mapData: _decodeString( + object.value['mapData'] ?? _missing(path, 'mapData'), + '$path.mapData', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + themeOverridePalette: + (object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette')) + is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette'), + '$path.themeOverridePalette', + ), + themeProfileId: + (object.value['themeProfileId'] ?? _missing(path, 'themeProfileId')) + is ConvexNull + ? null + : _decodeString( + object.value['themeProfileId'] ?? + _missing(path, 'themeProfileId'), + '$path.themeProfileId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'mapData': ConvexString(mapData), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'role': ConvexString(role.wireName), + 'themeOverridePalette': themeOverridePalette == null + ? const ConvexNull() + : themeOverridePalette!.encode('$path.themeOverridePalette'), + 'themeProfileId': themeProfileId == null + ? const ConvexNull() + : ConvexString(themeProfileId!), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class StrategiesListForFolderResultItem { + const StrategiesListForFolderResultItem({ + required this.attackLabel, + required this.createdAt, + required this.folderPublicId, + required this.mapData, + required this.name, + required this.publicId, + required this.revision, + required this.role, + required this.themeOverridePalette, + required this.themeProfileId, + required this.updatedAt, + }); + final StrategiesListForFolderResultItemAttackLabel attackLabel; + final double createdAt; + final String? folderPublicId; + final String mapData; + final String name; + final String publicId; + final double revision; + final FoldersListTreeResultItemRole role; + final OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette? + themeOverridePalette; + final String? themeProfileId; + final double updatedAt; + + factory StrategiesListForFolderResultItem.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'attackLabel', + 'createdAt', + 'folderPublicId', + 'mapData', + 'name', + 'publicId', + 'revision', + 'role', + 'themeOverridePalette', + 'themeProfileId', + 'updatedAt', + }); + return StrategiesListForFolderResultItem( + attackLabel: StrategiesListForFolderResultItemAttackLabel.fromWireName( + _decodeString( + object.value['attackLabel'] ?? _missing(path, 'attackLabel'), + '$path.attackLabel', + ), + '$path.attackLabel', + ), + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + folderPublicId: + (object.value['folderPublicId'] ?? _missing(path, 'folderPublicId')) + is ConvexNull + ? null + : _decodeString( + object.value['folderPublicId'] ?? + _missing(path, 'folderPublicId'), + '$path.folderPublicId', + ), + mapData: _decodeString( + object.value['mapData'] ?? _missing(path, 'mapData'), + '$path.mapData', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + role: FoldersListTreeResultItemRole.fromWireName( + _decodeString( + object.value['role'] ?? _missing(path, 'role'), + '$path.role', + ), + '$path.role', + ), + themeOverridePalette: + (object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette')) + is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette.decode( + object.value['themeOverridePalette'] ?? + _missing(path, 'themeOverridePalette'), + '$path.themeOverridePalette', + ), + themeProfileId: + (object.value['themeProfileId'] ?? _missing(path, 'themeProfileId')) + is ConvexNull + ? null + : _decodeString( + object.value['themeProfileId'] ?? + _missing(path, 'themeProfileId'), + '$path.themeProfileId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'attackLabel': ConvexString(attackLabel.wireName), + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'folderPublicId': folderPublicId == null + ? const ConvexNull() + : ConvexString(folderPublicId!), + 'mapData': ConvexString(mapData), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'role': ConvexString(role.wireName), + 'themeOverridePalette': themeOverridePalette == null + ? const ConvexNull() + : themeOverridePalette!.encode('$path.themeOverridePalette'), + 'themeProfileId': themeProfileId == null + ? const ConvexNull() + : ConvexString(themeProfileId!), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class StrategyGetFullSnapshotResult { + const StrategyGetFullSnapshotResult({ + required this.assets, + required this.elements, + required this.header, + required this.lineups, + required this.pages, + }); + final List assets; + final List elements; + final StrategiesGetHeaderResult header; + final List lineups; + final List pages; + + factory StrategyGetFullSnapshotResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'assets', + 'elements', + 'header', + 'lineups', + 'pages', + }); + return StrategyGetFullSnapshotResult( + assets: + _decodeArray( + object.value['assets'] ?? _missing(path, 'assets'), + '$path.assets', + ).value.indexed + .map( + (entry) => ImagesListForStrategyResultItem.decode( + entry.$2, + _indexPath('$path.assets', entry.$1), + ), + ) + .toList(growable: false), + elements: + _decodeArray( + object.value['elements'] ?? _missing(path, 'elements'), + '$path.elements', + ).value.indexed + .map( + (entry) => ElementsListForPageResultItem.decode( + entry.$2, + _indexPath('$path.elements', entry.$1), + ), + ) + .toList(growable: false), + header: StrategiesGetHeaderResult.decode( + object.value['header'] ?? _missing(path, 'header'), + '$path.header', + ), + lineups: + _decodeArray( + object.value['lineups'] ?? _missing(path, 'lineups'), + '$path.lineups', + ).value.indexed + .map( + (entry) => LineupsListForPageResultItem.decode( + entry.$2, + _indexPath('$path.lineups', entry.$1), + ), + ) + .toList(growable: false), + pages: + _decodeArray( + object.value['pages'] ?? _missing(path, 'pages'), + '$path.pages', + ).value.indexed + .map( + (entry) => StrategyGetFullSnapshotResultPagesItem.decode( + entry.$2, + _indexPath('$path.pages', entry.$1), + ), + ) + .toList(growable: false), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'assets': ConvexArray( + assets.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.assets', entry.$1)), + ) + .toList(growable: false), + ), + 'elements': ConvexArray( + elements.indexed + .map( + (entry) => + entry.$2.encode(_indexPath('$path.elements', entry.$1)), + ) + .toList(growable: false), + ), + 'header': header.encode('$path.header'), + 'lineups': ConvexArray( + lineups.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.lineups', entry.$1)), + ) + .toList(growable: false), + ), + 'pages': ConvexArray( + pages.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.pages', entry.$1)), + ) + .toList(growable: false), + ), + }); + } +} + +final class StrategyGetFullSnapshotResultPagesItem { + const StrategyGetFullSnapshotResultPagesItem({ + required this.contentCreatedAt, + required this.contentRevision, + required this.contentUpdatedAt, + required this.createdAt, + required this.isAttack, + required this.name, + required this.publicId, + required this.revision, + required this.settings, + required this.sortIndex, + required this.strategyPublicId, + required this.updatedAt, + }); + final double contentCreatedAt; + final double contentRevision; + final double contentUpdatedAt; + final double createdAt; + final bool isAttack; + final String name; + final String publicId; + final double revision; + final OpsApplyBatchArgsOpsItemPageAddPayloadSettings? settings; + final double sortIndex; + final String strategyPublicId; + final double updatedAt; + + factory StrategyGetFullSnapshotResultPagesItem.decode( + ConvexValue value, + String path, + ) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'contentCreatedAt', + 'contentRevision', + 'contentUpdatedAt', + 'createdAt', + 'isAttack', + 'name', + 'publicId', + 'revision', + 'settings', + 'sortIndex', + 'strategyPublicId', + 'updatedAt', + }); + return StrategyGetFullSnapshotResultPagesItem( + contentCreatedAt: _decodeNumber( + object.value['contentCreatedAt'] ?? _missing(path, 'contentCreatedAt'), + '$path.contentCreatedAt', + ), + contentRevision: _decodeNumber( + object.value['contentRevision'] ?? _missing(path, 'contentRevision'), + '$path.contentRevision', + ), + contentUpdatedAt: _decodeNumber( + object.value['contentUpdatedAt'] ?? _missing(path, 'contentUpdatedAt'), + '$path.contentUpdatedAt', + ), + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + isAttack: _decodeBoolean( + object.value['isAttack'] ?? _missing(path, 'isAttack'), + '$path.isAttack', + ), + name: _decodeString( + object.value['name'] ?? _missing(path, 'name'), + '$path.name', + ), + publicId: _decodeString( + object.value['publicId'] ?? _missing(path, 'publicId'), + '$path.publicId', + ), + revision: _decodeNumber( + object.value['revision'] ?? _missing(path, 'revision'), + '$path.revision', + ), + settings: + (object.value['settings'] ?? _missing(path, 'settings')) is ConvexNull + ? null + : OpsApplyBatchArgsOpsItemPageAddPayloadSettings.decode( + object.value['settings'] ?? _missing(path, 'settings'), + '$path.settings', + ), + sortIndex: _decodeNumber( + object.value['sortIndex'] ?? _missing(path, 'sortIndex'), + '$path.sortIndex', + ), + strategyPublicId: _decodeString( + object.value['strategyPublicId'] ?? _missing(path, 'strategyPublicId'), + '$path.strategyPublicId', + ), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'contentCreatedAt': _encodeNumber( + contentCreatedAt, + '$path.contentCreatedAt', + ), + 'contentRevision': _encodeNumber( + contentRevision, + '$path.contentRevision', + ), + 'contentUpdatedAt': _encodeNumber( + contentUpdatedAt, + '$path.contentUpdatedAt', + ), + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'isAttack': ConvexBoolean(isAttack), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + 'revision': _encodeNumber(revision, '$path.revision'), + 'settings': settings == null + ? const ConvexNull() + : settings!.encode('$path.settings'), + 'sortIndex': _encodeNumber(sortIndex, '$path.sortIndex'), + 'strategyPublicId': ConvexString(strategyPublicId), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +final class StrategyGetShellResult { + const StrategyGetShellResult({required this.header, required this.pages}); + final StrategiesGetHeaderResult header; + final List pages; + + factory StrategyGetShellResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const {'header', 'pages'}); + return StrategyGetShellResult( + header: StrategiesGetHeaderResult.decode( + object.value['header'] ?? _missing(path, 'header'), + '$path.header', + ), + pages: + _decodeArray( + object.value['pages'] ?? _missing(path, 'pages'), + '$path.pages', + ).value.indexed + .map( + (entry) => PageGetSnapshotResultPage.decode( + entry.$2, + _indexPath('$path.pages', entry.$1), + ), + ) + .toList(growable: false), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'header': header.encode('$path.header'), + 'pages': ConvexArray( + pages.indexed + .map( + (entry) => entry.$2.encode(_indexPath('$path.pages', entry.$1)), + ) + .toList(growable: false), + ), + }); + } +} + +final class UsersMeResult { + const UsersMeResult({ + required this.avatarUrl, + required this.createdAt, + required this.displayName, + required this.externalId, + required this.id, + required this.updatedAt, + }); + final String? avatarUrl; + final double createdAt; + final String displayName; + final String externalId; + final String id; + final double updatedAt; + + factory UsersMeResult.decode(ConvexValue value, String path) { + final object = _decodeObject(value, path); + _checkObjectFields(object, path, const { + 'avatarUrl', + 'createdAt', + 'displayName', + 'externalId', + 'id', + 'updatedAt', + }); + return UsersMeResult( + avatarUrl: + (object.value['avatarUrl'] ?? _missing(path, 'avatarUrl')) + is ConvexNull + ? null + : _decodeString( + object.value['avatarUrl'] ?? _missing(path, 'avatarUrl'), + '$path.avatarUrl', + ), + createdAt: _decodeNumber( + object.value['createdAt'] ?? _missing(path, 'createdAt'), + '$path.createdAt', + ), + displayName: _decodeString( + object.value['displayName'] ?? _missing(path, 'displayName'), + '$path.displayName', + ), + externalId: _decodeString( + object.value['externalId'] ?? _missing(path, 'externalId'), + '$path.externalId', + ), + id: _decodeString(object.value['id'] ?? _missing(path, 'id'), '$path.id'), + updatedAt: _decodeNumber( + object.value['updatedAt'] ?? _missing(path, 'updatedAt'), + '$path.updatedAt', + ), + ); + } + + ConvexObject encode(String path) { + return ConvexObject({ + 'avatarUrl': avatarUrl == null + ? const ConvexNull() + : ConvexString(avatarUrl!), + 'createdAt': _encodeNumber(createdAt, '$path.createdAt'), + 'displayName': ConvexString(displayName), + 'externalId': ConvexString(externalId), + 'id': ConvexString(id), + 'updatedAt': _encodeNumber(updatedAt, '$path.updatedAt'), + }); + } +} + +CloudPayload _decodeElementsListForPageResultItemPayload( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'agent' => _decodePayload( + () => const AgentConvexCodec().decode(value), + path, + ), + 'ability' => _decodePayload( + () => const AbilityConvexCodec().decode(value), + path, + ), + 'drawing' => _decodePayload( + () => const DrawingConvexCodec().decode(value), + path, + ), + 'text' => _decodePayload(() => const TextConvexCodec().decode(value), path), + 'image' => _decodePayload( + () => const ImageConvexCodec().decode(value), + path, + ), + 'utility' => _decodePayload( + () => const UtilityConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeElementsListForPageResultItemPayload( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'agent' => _encodePayload( + () => const AgentConvexCodec().encode(value), + path, + ), + 'ability' => _encodePayload( + () => const AbilityConvexCodec().encode(value), + path, + ), + 'drawing' => _encodePayload( + () => const DrawingConvexCodec().encode(value), + path, + ), + 'text' => _encodePayload(() => const TextConvexCodec().encode(value), path), + 'image' => _encodePayload( + () => const ImageConvexCodec().encode(value), + path, + ), + 'utility' => _encodePayload( + () => const UtilityConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +CloudPayload _decodeOpsApplyBatchArgsOpsItemElementAddPayload( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'agent' => _decodePayload( + () => const AgentConvexCodec().decode(value), + path, + ), + 'ability' => _decodePayload( + () => const AbilityConvexCodec().decode(value), + path, + ), + 'drawing' => _decodePayload( + () => const DrawingConvexCodec().decode(value), + path, + ), + 'text' => _decodePayload(() => const TextConvexCodec().decode(value), path), + 'image' => _decodePayload( + () => const ImageConvexCodec().decode(value), + path, + ), + 'utility' => _decodePayload( + () => const UtilityConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeOpsApplyBatchArgsOpsItemElementAddPayload( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'agent' => _encodePayload( + () => const AgentConvexCodec().encode(value), + path, + ), + 'ability' => _encodePayload( + () => const AbilityConvexCodec().encode(value), + path, + ), + 'drawing' => _encodePayload( + () => const DrawingConvexCodec().encode(value), + path, + ), + 'text' => _encodePayload(() => const TextConvexCodec().encode(value), path), + 'image' => _encodePayload( + () => const ImageConvexCodec().encode(value), + path, + ), + 'utility' => _encodePayload( + () => const UtilityConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +CloudPayload _decodeOpsApplyBatchArgsOpsItemElementPatchPayload( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'agent' => _decodePayload( + () => const AgentConvexCodec().decode(value), + path, + ), + 'ability' => _decodePayload( + () => const AbilityConvexCodec().decode(value), + path, + ), + 'drawing' => _decodePayload( + () => const DrawingConvexCodec().decode(value), + path, + ), + 'text' => _decodePayload(() => const TextConvexCodec().decode(value), path), + 'image' => _decodePayload( + () => const ImageConvexCodec().decode(value), + path, + ), + 'utility' => _decodePayload( + () => const UtilityConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeOpsApplyBatchArgsOpsItemElementPatchPayload( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'agent' => _encodePayload( + () => const AgentConvexCodec().encode(value), + path, + ), + 'ability' => _encodePayload( + () => const AbilityConvexCodec().encode(value), + path, + ), + 'drawing' => _encodePayload( + () => const DrawingConvexCodec().encode(value), + path, + ), + 'text' => _encodePayload(() => const TextConvexCodec().encode(value), path), + 'image' => _encodePayload( + () => const ImageConvexCodec().encode(value), + path, + ), + 'utility' => _encodePayload( + () => const UtilityConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +CloudPayload _decodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( + ConvexValue value, + String path, +) { + final object = _decodeObject(value, path); + final tag = _decodeString( + object.value['kind'] ?? _missing(path, 'kind'), + '$path.kind', + ); + return switch (tag) { + 'agent' => _decodePayload( + () => const AgentConvexCodec().decode(value), + path, + ), + 'ability' => _decodePayload( + () => const AbilityConvexCodec().decode(value), + path, + ), + 'drawing' => _decodePayload( + () => const DrawingConvexCodec().decode(value), + path, + ), + 'text' => _decodePayload(() => const TextConvexCodec().decode(value), path), + 'image' => _decodePayload( + () => const ImageConvexCodec().decode(value), + path, + ), + 'utility' => _decodePayload( + () => const UtilityConvexCodec().decode(value), + path, + ), + _ => throw ConvexDecodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +ConvexValue _encodeOpsApplyBatchResultResultsItemRejectedCurrentElementValue( + CloudPayload value, + String path, +) { + final tag = value['kind']; + return switch (tag) { + 'agent' => _encodePayload( + () => const AgentConvexCodec().encode(value), + path, + ), + 'ability' => _encodePayload( + () => const AbilityConvexCodec().encode(value), + path, + ), + 'drawing' => _encodePayload( + () => const DrawingConvexCodec().encode(value), + path, + ), + 'text' => _encodePayload(() => const TextConvexCodec().encode(value), path), + 'image' => _encodePayload( + () => const ImageConvexCodec().encode(value), + path, + ), + 'utility' => _encodePayload( + () => const UtilityConvexCodec().encode(value), + path, + ), + _ => throw ConvexEncodingException( + '$path.kind', + 'unknown payload tag $tag', + ), + }; +} + +bool _validateFoldersCreateResult(ConvexValue value) => + (_matchesRaw0(value) || _matchesRaw2(value)); + +bool _matchesRaw0(ConvexValue value) => + value is ConvexObject && + value.value.keys.every(const {'ok'}.contains) && + (value.value['ok'] != null && _matchesRaw1(value.value['ok']!)); + +bool _matchesRaw1(ConvexValue value) => + value is ConvexBoolean && value.value == true; + +bool _matchesRaw2(ConvexValue value) => + value is ConvexObject && + value.value.keys.every(const {'ok', 'reused'}.contains) && + (value.value['ok'] != null && _matchesRaw1(value.value['ok']!)) && + (value.value['reused'] != null && _matchesRaw1(value.value['reused']!)); + +bool _validateInvitesGetResult(ConvexValue value) => + (_matchesRaw3(value) || _matchesRaw12(value) || _matchesRaw6(value)); + +bool _matchesRaw3(ConvexValue value) => + value is ConvexObject && + value.value.keys.every( + const { + 'createdAt', + 'expiresAt', + 'hasAccessAlready', + 'inviteRole', + 'revoked', + 'strategyPublicId', + 'token', + }.contains, + ) && + (value.value['createdAt'] != null && + _matchesRaw4(value.value['createdAt']!)) && + (value.value['expiresAt'] != null && + _matchesRaw5(value.value['expiresAt']!)) && + (value.value['hasAccessAlready'] != null && + _matchesRaw7(value.value['hasAccessAlready']!)) && + (value.value['inviteRole'] != null && + _matchesRaw8(value.value['inviteRole']!)) && + (value.value['revoked'] != null && _matchesRaw7(value.value['revoked']!)) && + (value.value['strategyPublicId'] != null && + _matchesRaw11(value.value['strategyPublicId']!)) && + (value.value['token'] != null && _matchesRaw11(value.value['token']!)); + +bool _matchesRaw4(ConvexValue value) => + (value is ConvexFloat || value is ConvexInteger); + +bool _matchesRaw5(ConvexValue value) => + (_matchesRaw4(value) || _matchesRaw6(value)); + +bool _matchesRaw6(ConvexValue value) => value is ConvexNull; + +bool _matchesRaw7(ConvexValue value) => value is ConvexBoolean; + +bool _matchesRaw8(ConvexValue value) => + (_matchesRaw9(value) || _matchesRaw10(value)); + +bool _matchesRaw9(ConvexValue value) => + value is ConvexString && value.value == 'editor'; + +bool _matchesRaw10(ConvexValue value) => + value is ConvexString && value.value == 'viewer'; + +bool _matchesRaw11(ConvexValue value) => value is ConvexString; + +bool _matchesRaw12(ConvexValue value) => + value is ConvexArray && value.value.every((item) => _matchesRaw13(item)); + +bool _matchesRaw13(ConvexValue value) => + value is ConvexObject && + value.value.keys.every( + const { + 'createdAt', + 'expiresAt', + 'redeemed', + 'revokedAt', + 'role', + 'token', + }.contains, + ) && + (value.value['createdAt'] != null && + _matchesRaw4(value.value['createdAt']!)) && + (value.value['expiresAt'] != null && + _matchesRaw5(value.value['expiresAt']!)) && + (value.value['redeemed'] != null && + _matchesRaw7(value.value['redeemed']!)) && + (value.value['revokedAt'] != null && + _matchesRaw5(value.value['revokedAt']!)) && + (value.value['role'] != null && _matchesRaw8(value.value['role']!)) && + (value.value['token'] != null && _matchesRaw11(value.value['token']!)); + +bool _validatePagesAddResult(ConvexValue value) => + (_matchesRaw14(value) || _matchesRaw15(value)); + +bool _matchesRaw14(ConvexValue value) => + value is ConvexObject && + value.value.keys.every(const {'ok', 'revision'}.contains) && + (value.value['ok'] != null && _matchesRaw1(value.value['ok']!)) && + (value.value['revision'] != null && _matchesRaw4(value.value['revision']!)); + +bool _matchesRaw15(ConvexValue value) => + value is ConvexObject && + value.value.keys.every(const {'ok', 'reused', 'revision'}.contains) && + (value.value['ok'] != null && _matchesRaw1(value.value['ok']!)) && + (value.value['reused'] != null && _matchesRaw1(value.value['reused']!)) && + (value.value['revision'] != null && _matchesRaw4(value.value['revision']!)); + +ConvexObject encodeElementsListForPageArgs({ + required String pagePublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'pagePublicId': ConvexString(pagePublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +List decodeElementsListForPageResult( + ConvexValue value, +) => _decodeArray(value, 'elements.js:listForPage.returns').value.indexed + .map( + (entry) => ElementsListForPageResultItem.decode( + entry.$2, + _indexPath('elements.js:listForPage.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeElementsListForStrategyArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +List decodeElementsListForStrategyResult( + ConvexValue value, +) => _decodeArray(value, 'elements.js:listForStrategy.returns').value.indexed + .map( + (entry) => ElementsListForPageResultItem.decode( + entry.$2, + _indexPath('elements.js:listForStrategy.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeFoldersCreateArgs({ + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + required String name, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), + required String publicId, +}) => ConvexObject({ + if (color.isPresent) 'color': ConvexString(color.value), + if (customColorValue.isPresent) + 'customColorValue': _encodeNumber( + customColorValue.value, + 'folders.js:create.args.customColorValue', + ), + if (iconCodePoint.isPresent) + 'iconCodePoint': _encodeNumber( + iconCodePoint.value, + 'folders.js:create.args.iconCodePoint', + ), + if (iconFontFamily.isPresent) + 'iconFontFamily': ConvexString(iconFontFamily.value), + if (iconFontPackage.isPresent) + 'iconFontPackage': ConvexString(iconFontPackage.value), + if (iconId.isPresent) + 'iconId': _encodeNumber(iconId.value, 'folders.js:create.args.iconId'), + 'name': ConvexString(name), + if (parentFolderPublicId.isPresent) + 'parentFolderPublicId': ConvexString(parentFolderPublicId.value), + 'publicId': ConvexString(publicId), +}); + +ConvexValue decodeFoldersCreateResult(ConvexValue value) => _decodeRaw( + value, + 'folders.js:create.returns', + _validateFoldersCreateResult, +); + +ConvexObject encodeFoldersDeleteArgs({required String folderPublicId}) => + ConvexObject({'folderPublicId': ConvexString(folderPublicId)}); + +FoldersDeleteResult decodeFoldersDeleteResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'folders.js:delete.returns'); + +ConvexObject encodeFoldersListTreeArgs({ + ConvexOptional scope = + const ConvexOptional.absent(), +}) => ConvexObject({ + if (scope.isPresent) 'scope': ConvexString(scope.value.wireName), +}); + +List decodeFoldersListTreeResult( + ConvexValue value, +) => _decodeArray(value, 'folders.js:listTree.returns').value.indexed + .map( + (entry) => FoldersListTreeResultItem.decode( + entry.$2, + _indexPath('folders.js:listTree.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeFoldersMoveArgs({ + required String folderPublicId, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), +}) => ConvexObject({ + 'folderPublicId': ConvexString(folderPublicId), + if (parentFolderPublicId.isPresent) + 'parentFolderPublicId': ConvexString(parentFolderPublicId.value), +}); + +FoldersDeleteResult decodeFoldersMoveResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'folders.js:move.returns'); + +ConvexObject encodeFoldersUpdateArgs({ + ConvexOptional clearCustomColorValue = const ConvexOptional.absent(), + ConvexOptional clearIconFontFamily = const ConvexOptional.absent(), + ConvexOptional clearIconFontPackage = const ConvexOptional.absent(), + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + required String folderPublicId, + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), +}) => ConvexObject({ + if (clearCustomColorValue.isPresent) + 'clearCustomColorValue': ConvexBoolean(clearCustomColorValue.value), + if (clearIconFontFamily.isPresent) + 'clearIconFontFamily': ConvexBoolean(clearIconFontFamily.value), + if (clearIconFontPackage.isPresent) + 'clearIconFontPackage': ConvexBoolean(clearIconFontPackage.value), + if (color.isPresent) 'color': ConvexString(color.value), + if (customColorValue.isPresent) + 'customColorValue': _encodeNumber( + customColorValue.value, + 'folders.js:update.args.customColorValue', + ), + 'folderPublicId': ConvexString(folderPublicId), + if (iconCodePoint.isPresent) + 'iconCodePoint': _encodeNumber( + iconCodePoint.value, + 'folders.js:update.args.iconCodePoint', + ), + if (iconFontFamily.isPresent) + 'iconFontFamily': ConvexString(iconFontFamily.value), + if (iconFontPackage.isPresent) + 'iconFontPackage': ConvexString(iconFontPackage.value), + if (iconId.isPresent) + 'iconId': _encodeNumber(iconId.value, 'folders.js:update.args.iconId'), + if (name.isPresent) 'name': ConvexString(name.value), +}); + +FoldersDeleteResult decodeFoldersUpdateResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'folders.js:update.returns'); + +ConvexObject encodeHealthPingArgs() => ConvexObject({}); + +HealthPingResult decodeHealthPingResult(ConvexValue value) => + HealthPingResult.fromWireName( + _decodeString(value, 'health.js:ping.returns'), + 'health.js:ping.returns', + ); + +ConvexObject encodeImagesCompleteUploadArgs({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + ConvexOptional etag = const ConvexOptional.absent(), + ConvexOptional fileExtension = const ConvexOptional.absent(), + ConvexOptional height = const ConvexOptional.absent(), + ConvexOptional mimeType = const ConvexOptional.absent(), + ConvexOptional objectKey = const ConvexOptional.absent(), + ConvexOptional provider = + const ConvexOptional.absent(), + ConvexOptional storageId = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional uploadId = const ConvexOptional.absent(), + ConvexOptional width = const ConvexOptional.absent(), +}) => ConvexObject({ + 'assetPublicId': ConvexString(assetPublicId), + if (byteSize.isPresent) + 'byteSize': _encodeNumber( + byteSize.value, + 'images.js:completeUpload.args.byteSize', + ), + if (etag.isPresent) 'etag': ConvexString(etag.value), + if (fileExtension.isPresent) + 'fileExtension': ConvexString(fileExtension.value), + if (height.isPresent) + 'height': _encodeNumber( + height.value, + 'images.js:completeUpload.args.height', + ), + if (mimeType.isPresent) 'mimeType': ConvexString(mimeType.value), + if (objectKey.isPresent) 'objectKey': ConvexString(objectKey.value), + if (provider.isPresent) 'provider': ConvexString(provider.value.wireName), + if (storageId.isPresent) 'storageId': ConvexString(storageId.value), + 'strategyPublicId': ConvexString(strategyPublicId), + if (uploadId.isPresent) 'uploadId': ConvexString(uploadId.value), + if (width.isPresent) + 'width': _encodeNumber(width.value, 'images.js:completeUpload.args.width'), +}); + +ImagesCompleteUploadResult decodeImagesCompleteUploadResult( + ConvexValue value, +) => ImagesCompleteUploadResult.decode( + value, + 'images.js:completeUpload.returns', +); + +ConvexObject encodeImagesDeleteAssetRefArgs({ + required String assetPublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'assetPublicId': ConvexString(assetPublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +FoldersDeleteResult decodeImagesDeleteAssetRefResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'images.js:deleteAssetRef.returns'); + +ConvexObject encodeImagesGenerateUploadUrlArgs({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + required String fileExtension, + ConvexOptional height = const ConvexOptional.absent(), + required String mimeType, + required String strategyPublicId, + ConvexOptional width = const ConvexOptional.absent(), +}) => ConvexObject({ + 'assetPublicId': ConvexString(assetPublicId), + if (byteSize.isPresent) + 'byteSize': _encodeNumber( + byteSize.value, + 'images.js:generateUploadUrl.args.byteSize', + ), + 'fileExtension': ConvexString(fileExtension), + if (height.isPresent) + 'height': _encodeNumber( + height.value, + 'images.js:generateUploadUrl.args.height', + ), + 'mimeType': ConvexString(mimeType), + 'strategyPublicId': ConvexString(strategyPublicId), + if (width.isPresent) + 'width': _encodeNumber( + width.value, + 'images.js:generateUploadUrl.args.width', + ), +}); + +ImagesGenerateUploadUrlResult decodeImagesGenerateUploadUrlResult( + ConvexValue value, +) => ImagesGenerateUploadUrlResult.decode( + value, + 'images.js:generateUploadUrl.returns', +); + +ConvexObject encodeImagesGetAssetUrlArgs({ + required String assetPublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'assetPublicId': ConvexString(assetPublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ImagesGetAssetUrlResult decodeImagesGetAssetUrlResult(ConvexValue value) => + ImagesGetAssetUrlResult.decode(value, 'images.js:getAssetUrl.returns'); + +ConvexObject encodeImagesListForStrategyArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +List decodeImagesListForStrategyResult( + ConvexValue value, +) => _decodeArray(value, 'images.js:listForStrategy.returns').value.indexed + .map( + (entry) => ImagesListForStrategyResultItem.decode( + entry.$2, + _indexPath('images.js:listForStrategy.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeInvitesCreateArgs({ + ConvexOptional expiresAt = const ConvexOptional.absent(), + required InvitesCreateArgsRole role, + required String strategyPublicId, + required String token, +}) => ConvexObject({ + if (expiresAt.isPresent) + 'expiresAt': _encodeNumber( + expiresAt.value, + 'invites.js:create.args.expiresAt', + ), + 'role': ConvexString(role.wireName), + 'strategyPublicId': ConvexString(strategyPublicId), + 'token': ConvexString(token), +}); + +FoldersDeleteResult decodeInvitesCreateResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'invites.js:create.returns'); + +ConvexObject encodeInvitesGetArgs({ + ConvexOptional strategyPublicId = const ConvexOptional.absent(), + ConvexOptional token = const ConvexOptional.absent(), +}) => ConvexObject({ + if (strategyPublicId.isPresent) + 'strategyPublicId': ConvexString(strategyPublicId.value), + if (token.isPresent) 'token': ConvexString(token.value), +}); + +ConvexValue decodeInvitesGetResult(ConvexValue value) => + _decodeRaw(value, 'invites.js:get.returns', _validateInvitesGetResult); + +ConvexObject encodeInvitesRedeemArgs({required String token}) => + ConvexObject({'token': ConvexString(token)}); + +InvitesRedeemResult decodeInvitesRedeemResult(ConvexValue value) => + InvitesRedeemResult.decode(value, 'invites.js:redeem.returns'); + +ConvexObject encodeInvitesRevokeArgs({ + required String strategyPublicId, + required String token, +}) => ConvexObject({ + 'strategyPublicId': ConvexString(strategyPublicId), + 'token': ConvexString(token), +}); + +FoldersDeleteResult decodeInvitesRevokeResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'invites.js:revoke.returns'); + +ConvexObject encodeLineupsListForPageArgs({ + required String pagePublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'pagePublicId': ConvexString(pagePublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +List decodeLineupsListForPageResult( + ConvexValue value, +) => _decodeArray(value, 'lineups.js:listForPage.returns').value.indexed + .map( + (entry) => LineupsListForPageResultItem.decode( + entry.$2, + _indexPath('lineups.js:listForPage.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeLineupsListForStrategyArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +List decodeLineupsListForStrategyResult( + ConvexValue value, +) => _decodeArray(value, 'lineups.js:listForStrategy.returns').value.indexed + .map( + (entry) => LineupsListForPageResultItem.decode( + entry.$2, + _indexPath('lineups.js:listForStrategy.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeOpsApplyBatchArgs({ + required String clientId, + required double clientProtocolVersion, + required List ops, + required String strategyPublicId, +}) => ConvexObject({ + 'clientId': ConvexString(clientId), + 'clientProtocolVersion': _encodeNumber( + clientProtocolVersion, + 'ops.js:applyBatch.args.clientProtocolVersion', + ), + 'ops': ConvexArray( + ops.indexed + .map( + (entry) => entry.$2.encode( + _indexPath('ops.js:applyBatch.args.ops', entry.$1), + ), + ) + .toList(growable: false), + ), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +OpsApplyBatchResult decodeOpsApplyBatchResult(ConvexValue value) => + OpsApplyBatchResult.decode(value, 'ops.js:applyBatch.returns'); + +ConvexObject encodePageGetSnapshotArgs({ + required String pagePublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'pagePublicId': ConvexString(pagePublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +PageGetSnapshotResult decodePageGetSnapshotResult(ConvexValue value) => + PageGetSnapshotResult.decode(value, 'page.js:getSnapshot.returns'); + +ConvexObject encodePagesAddArgs({ + required double expectedRevision, + required bool isAttack, + required String name, + required String pagePublicId, + ConvexOptional settings = + const ConvexOptional.absent(), + required double sortIndex, + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'pages.js:add.args.expectedRevision', + ), + 'isAttack': ConvexBoolean(isAttack), + 'name': ConvexString(name), + 'pagePublicId': ConvexString(pagePublicId), + if (settings.isPresent) + 'settings': settings.value.encode('pages.js:add.args.settings'), + 'sortIndex': _encodeNumber(sortIndex, 'pages.js:add.args.sortIndex'), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ConvexValue decodePagesAddResult(ConvexValue value) => + _decodeRaw(value, 'pages.js:add.returns', _validatePagesAddResult); + +ConvexObject encodePagesDeleteArgs({ + required double expectedRevision, + required String pagePublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'pages.js:delete.args.expectedRevision', + ), + 'pagePublicId': ConvexString(pagePublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ConvexValue decodePagesDeleteResult(ConvexValue value) => + _decodeRaw(value, 'pages.js:delete.returns', _validatePagesAddResult); + +ConvexObject encodePagesListForStrategyArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +List decodePagesListForStrategyResult( + ConvexValue value, +) => _decodeArray(value, 'pages.js:listForStrategy.returns').value.indexed + .map( + (entry) => PageGetSnapshotResultPage.decode( + entry.$2, + _indexPath('pages.js:listForStrategy.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodePagesRenameArgs({ + required double expectedRevision, + required String name, + required String pagePublicId, + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'pages.js:rename.args.expectedRevision', + ), + 'name': ConvexString(name), + 'pagePublicId': ConvexString(pagePublicId), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ConvexValue decodePagesRenameResult(ConvexValue value) => + _decodeRaw(value, 'pages.js:rename.returns', _validatePagesAddResult); + +ConvexObject encodePagesReorderArgs({ + required double expectedRevision, + required List orderedPagePublicIds, + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'pages.js:reorder.args.expectedRevision', + ), + 'orderedPagePublicIds': ConvexArray( + orderedPagePublicIds.indexed + .map((entry) => ConvexString(entry.$2)) + .toList(growable: false), + ), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ConvexValue decodePagesReorderResult(ConvexValue value) => + _decodeRaw(value, 'pages.js:reorder.returns', _validatePagesAddResult); + +ConvexObject encodeSharesCreateArgs({ + required InvitesCreateArgsRole role, + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, +}) => ConvexObject({ + 'role': ConvexString(role.wireName), + 'targetPublicId': ConvexString(targetPublicId), + 'targetType': ConvexString(targetType.wireName), + 'token': ConvexString(token), +}); + +FoldersDeleteResult decodeSharesCreateResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'shares.js:create.returns'); + +ConvexObject encodeSharesListArgs({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, +}) => ConvexObject({ + 'targetPublicId': ConvexString(targetPublicId), + 'targetType': ConvexString(targetType.wireName), +}); + +List decodeSharesListResult(ConvexValue value) => + _decodeArray(value, 'shares.js:list.returns').value.indexed + .map( + (entry) => SharesListResultItem.decode( + entry.$2, + _indexPath('shares.js:list.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeSharesRedeemArgs({required String token}) => + ConvexObject({'token': ConvexString(token)}); + +SharesRedeemResult decodeSharesRedeemResult(ConvexValue value) => + SharesRedeemResult.decode(value, 'shares.js:redeem.returns'); + +ConvexObject encodeSharesRevokeArgs({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, +}) => ConvexObject({ + 'targetPublicId': ConvexString(targetPublicId), + 'targetType': ConvexString(targetType.wireName), + 'token': ConvexString(token), +}); + +FoldersDeleteResult decodeSharesRevokeResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'shares.js:revoke.returns'); + +ConvexObject encodeStrategiesCreateArgs({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), +}) => ConvexObject({ + if (folderPublicId.isPresent) + 'folderPublicId': ConvexString(folderPublicId.value), + 'mapData': ConvexString(mapData), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + if (themeOverridePalette.isPresent) + 'themeOverridePalette': themeOverridePalette.value.encode( + 'strategies.js:create.args.themeOverridePalette', + ), + if (themeProfileId.isPresent) + 'themeProfileId': ConvexString(themeProfileId.value), +}); + +ConvexValue decodeStrategiesCreateResult(ConvexValue value) => _decodeRaw( + value, + 'strategies.js:create.returns', + _validateFoldersCreateResult, +); + +ConvexObject encodeStrategiesCreateWithInitialPageArgs({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required bool initialPageIsAttack, + required String initialPageName, + required String initialPagePublicId, + ConvexOptional + initialPageSettings = + const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), +}) => ConvexObject({ + if (folderPublicId.isPresent) + 'folderPublicId': ConvexString(folderPublicId.value), + 'initialPageIsAttack': ConvexBoolean(initialPageIsAttack), + 'initialPageName': ConvexString(initialPageName), + 'initialPagePublicId': ConvexString(initialPagePublicId), + if (initialPageSettings.isPresent) + 'initialPageSettings': initialPageSettings.value.encode( + 'strategies.js:createWithInitialPage.args.initialPageSettings', + ), + 'mapData': ConvexString(mapData), + 'name': ConvexString(name), + 'publicId': ConvexString(publicId), + if (themeOverridePalette.isPresent) + 'themeOverridePalette': themeOverridePalette.value.encode( + 'strategies.js:createWithInitialPage.args.themeOverridePalette', + ), + if (themeProfileId.isPresent) + 'themeProfileId': ConvexString(themeProfileId.value), +}); + +ConvexValue decodeStrategiesCreateWithInitialPageResult(ConvexValue value) => + _decodeRaw( + value, + 'strategies.js:createWithInitialPage.returns', + _validateFoldersCreateResult, + ); + +ConvexObject encodeStrategiesDeleteArgs({ + required double expectedRevision, + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'strategies.js:delete.args.expectedRevision', + ), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +FoldersDeleteResult decodeStrategiesDeleteResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'strategies.js:delete.returns'); + +ConvexObject encodeStrategiesGetHeaderArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +StrategiesGetHeaderResult decodeStrategiesGetHeaderResult(ConvexValue value) => + StrategiesGetHeaderResult.decode(value, 'strategies.js:getHeader.returns'); + +ConvexObject encodeStrategiesListForFolderArgs({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + ConvexOptional scope = + const ConvexOptional.absent(), +}) => ConvexObject({ + if (folderPublicId.isPresent) + 'folderPublicId': ConvexString(folderPublicId.value), + if (scope.isPresent) 'scope': ConvexString(scope.value.wireName), +}); + +List decodeStrategiesListForFolderResult( + ConvexValue value, +) => _decodeArray(value, 'strategies.js:listForFolder.returns').value.indexed + .map( + (entry) => StrategiesListForFolderResultItem.decode( + entry.$2, + _indexPath('strategies.js:listForFolder.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeStrategiesListSharedWithMeArgs() => ConvexObject({}); + +List decodeStrategiesListSharedWithMeResult( + ConvexValue value, +) => _decodeArray(value, 'strategies.js:listSharedWithMe.returns').value.indexed + .map( + (entry) => StrategiesListForFolderResultItem.decode( + entry.$2, + _indexPath('strategies.js:listSharedWithMe.returns', entry.$1), + ), + ) + .toList(growable: false); + +ConvexObject encodeStrategiesMoveArgs({ + required double expectedRevision, + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String strategyPublicId, +}) => ConvexObject({ + 'expectedRevision': _encodeNumber( + expectedRevision, + 'strategies.js:move.args.expectedRevision', + ), + if (folderPublicId.isPresent) + 'folderPublicId': ConvexString(folderPublicId.value), + 'strategyPublicId': ConvexString(strategyPublicId), +}); + +ConvexValue decodeStrategiesMoveResult(ConvexValue value) => + _decodeRaw(value, 'strategies.js:move.returns', _validatePagesAddResult); + +ConvexObject encodeStrategiesUpdateArgs({ + ConvexOptional clearThemeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional clearThemeProfileId = const ConvexOptional.absent(), + required double expectedRevision, + ConvexOptional mapData = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), +}) => ConvexObject({ + if (clearThemeOverridePalette.isPresent) + 'clearThemeOverridePalette': ConvexBoolean(clearThemeOverridePalette.value), + if (clearThemeProfileId.isPresent) + 'clearThemeProfileId': ConvexBoolean(clearThemeProfileId.value), + 'expectedRevision': _encodeNumber( + expectedRevision, + 'strategies.js:update.args.expectedRevision', + ), + if (mapData.isPresent) 'mapData': ConvexString(mapData.value), + if (name.isPresent) 'name': ConvexString(name.value), + 'strategyPublicId': ConvexString(strategyPublicId), + if (themeOverridePalette.isPresent) + 'themeOverridePalette': themeOverridePalette.value.encode( + 'strategies.js:update.args.themeOverridePalette', + ), + if (themeProfileId.isPresent) + 'themeProfileId': ConvexString(themeProfileId.value), +}); + +ConvexValue decodeStrategiesUpdateResult(ConvexValue value) => + _decodeRaw(value, 'strategies.js:update.returns', _validatePagesAddResult); + +ConvexObject encodeStrategyGetFullSnapshotArgs({ + required String strategyPublicId, +}) => ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +StrategyGetFullSnapshotResult decodeStrategyGetFullSnapshotResult( + ConvexValue value, +) => StrategyGetFullSnapshotResult.decode( + value, + 'strategy.js:getFullSnapshot.returns', +); + +ConvexObject encodeStrategyGetShellArgs({required String strategyPublicId}) => + ConvexObject({'strategyPublicId': ConvexString(strategyPublicId)}); + +StrategyGetShellResult decodeStrategyGetShellResult(ConvexValue value) => + StrategyGetShellResult.decode(value, 'strategy.js:getShell.returns'); + +ConvexObject encodeUsersEnsureCurrentUserArgs() => ConvexObject({}); + +FoldersDeleteResult decodeUsersEnsureCurrentUserResult(ConvexValue value) => + FoldersDeleteResult.decode(value, 'users.js:ensureCurrentUser.returns'); + +ConvexObject encodeUsersMeArgs() => ConvexObject({}); + +UsersMeResult? decodeUsersMeResult(ConvexValue value) => (value) is ConvexNull + ? null + : UsersMeResult.decode(value, 'users.js:me.returns'); diff --git a/lib/collab/generated/generated.dart b/lib/collab/generated/generated.dart new file mode 100644 index 00000000..247b303f --- /dev/null +++ b/lib/collab/generated/generated.dart @@ -0,0 +1,7 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated from convex/function_spec.json by tool/icarus_convex_codegen. +// ignore_for_file: prefer_const_constructors, unused_element, unused_import + +export 'convex_error_codes.dart'; +export 'convex_models.dart'; +export 'icarus_convex_api.dart'; diff --git a/lib/collab/generated/icarus_convex_api.dart b/lib/collab/generated/icarus_convex_api.dart new file mode 100644 index 00000000..c2d0dfa0 --- /dev/null +++ b/lib/collab/generated/icarus_convex_api.dart @@ -0,0 +1,1221 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated from convex/function_spec.json by tool/icarus_convex_codegen. +// ignore_for_file: prefer_const_constructors, unused_element, unused_import + +import 'dart:async'; +import 'dart:typed_data'; + +import '../transport/convex_transport.dart'; +import 'convex_error_codes.dart'; +import 'convex_models.dart'; + +final class ConvexQuery { + const ConvexQuery({ + required ConvexTransport transport, + required String name, + required ConvexObject args, + required T Function(ConvexValue) decode, + }) : _transport = transport, + _name = name, + _args = args, + _decode = decode; + + final ConvexTransport _transport; + final String _name; + final ConvexObject _args; + final T Function(ConvexValue) _decode; + + Future fetch() => _invoke(() => _transport.query(_name, _args), _decode); + + Stream watch() { + late final StreamController controller; + StreamSubscription? subscription; + var active = false; + + controller = StreamController( + onListen: () { + active = true; + subscription = _transport + .subscribe(_name, _args) + .listen( + (value) { + if (!active) return; + try { + controller.add(_decode(value)); + } catch (error, stackTrace) { + active = false; + controller.addError(error, stackTrace); + subscription?.cancel(); + controller.close(); + } + }, + onError: (Object error, StackTrace stackTrace) { + if (!active) return; + if (error is ConvexTransportError) { + controller.addError( + ConvexFunctionException.fromTransport(error), + stackTrace, + ); + return; + } + active = false; + controller.addError(error, stackTrace); + subscription?.cancel(); + controller.close(); + }, + onDone: () { + if (!active) return; + active = false; + controller.close(); + }, + ); + }, + onCancel: () { + active = false; + return subscription?.cancel(); + }, + ); + return controller.stream; + } +} + +Future _invoke( + Future Function() invoke, + T Function(ConvexValue) decode, +) async { + try { + return decode(await invoke()); + } on ConvexTransportError catch (error) { + throw ConvexFunctionException.fromTransport(error); + } +} + +abstract interface class IcarusConvexApi { + factory IcarusConvexApi(ConvexTransport transport) = _IcarusConvexApi; + ElementsModule get elements; + FoldersModule get folders; + HealthModule get health; + ImagesModule get images; + InvitesModule get invites; + LineupsModule get lineups; + OpsModule get ops; + PageModule get page; + PagesModule get pages; + SharesModule get shares; + StrategiesModule get strategies; + StrategyModule get strategy; + UsersModule get users; +} + +final class _IcarusConvexApi implements IcarusConvexApi { + _IcarusConvexApi(ConvexTransport transport) + : elements = _ElementsModule(transport), + folders = _FoldersModule(transport), + health = _HealthModule(transport), + images = _ImagesModule(transport), + invites = _InvitesModule(transport), + lineups = _LineupsModule(transport), + ops = _OpsModule(transport), + page = _PageModule(transport), + pages = _PagesModule(transport), + shares = _SharesModule(transport), + strategies = _StrategiesModule(transport), + strategy = _StrategyModule(transport), + users = _UsersModule(transport); + @override + final ElementsModule elements; + @override + final FoldersModule folders; + @override + final HealthModule health; + @override + final ImagesModule images; + @override + final InvitesModule invites; + @override + final LineupsModule lineups; + @override + final OpsModule ops; + @override + final PageModule page; + @override + final PagesModule pages; + @override + final SharesModule shares; + @override + final StrategiesModule strategies; + @override + final StrategyModule strategy; + @override + final UsersModule users; +} + +abstract interface class ElementsModule { + ConvexQuery> listForPage({ + required String pagePublicId, + required String strategyPublicId, + }); + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }); +} + +final class _ElementsModule implements ElementsModule { + const _ElementsModule(this._transport); + final ConvexTransport _transport; + @override + ConvexQuery> listForPage({ + required String pagePublicId, + required String strategyPublicId, + }) { + final args = encodeElementsListForPageArgs( + pagePublicId: pagePublicId, + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'elements:listForPage', + args: args, + decode: decodeElementsListForPageResult, + ); + } + + @override + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }) { + final args = encodeElementsListForStrategyArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'elements:listForStrategy', + args: args, + decode: decodeElementsListForStrategyResult, + ); + } +} + +abstract interface class FoldersModule { + Future create({ + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + required String name, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), + required String publicId, + }); + Future delete({required String folderPublicId}); + ConvexQuery> listTree({ + ConvexOptional scope = + const ConvexOptional.absent(), + }); + Future move({ + required String folderPublicId, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), + }); + Future update({ + ConvexOptional clearCustomColorValue = const ConvexOptional.absent(), + ConvexOptional clearIconFontFamily = const ConvexOptional.absent(), + ConvexOptional clearIconFontPackage = const ConvexOptional.absent(), + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + required String folderPublicId, + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), + }); +} + +final class _FoldersModule implements FoldersModule { + const _FoldersModule(this._transport); + final ConvexTransport _transport; + @override + Future create({ + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + required String name, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), + required String publicId, + }) { + final args = encodeFoldersCreateArgs( + color: color, + customColorValue: customColorValue, + iconCodePoint: iconCodePoint, + iconFontFamily: iconFontFamily, + iconFontPackage: iconFontPackage, + iconId: iconId, + name: name, + parentFolderPublicId: parentFolderPublicId, + publicId: publicId, + ); + return _invoke( + () => _transport.mutation('folders:create', args), + decodeFoldersCreateResult, + ); + } + + @override + Future delete({required String folderPublicId}) { + final args = encodeFoldersDeleteArgs(folderPublicId: folderPublicId); + return _invoke( + () => _transport.mutation('folders:delete', args), + decodeFoldersDeleteResult, + ); + } + + @override + ConvexQuery> listTree({ + ConvexOptional scope = + const ConvexOptional.absent(), + }) { + final args = encodeFoldersListTreeArgs(scope: scope); + return ConvexQuery( + transport: _transport, + name: 'folders:listTree', + args: args, + decode: decodeFoldersListTreeResult, + ); + } + + @override + Future move({ + required String folderPublicId, + ConvexOptional parentFolderPublicId = const ConvexOptional.absent(), + }) { + final args = encodeFoldersMoveArgs( + folderPublicId: folderPublicId, + parentFolderPublicId: parentFolderPublicId, + ); + return _invoke( + () => _transport.mutation('folders:move', args), + decodeFoldersMoveResult, + ); + } + + @override + Future update({ + ConvexOptional clearCustomColorValue = const ConvexOptional.absent(), + ConvexOptional clearIconFontFamily = const ConvexOptional.absent(), + ConvexOptional clearIconFontPackage = const ConvexOptional.absent(), + ConvexOptional color = const ConvexOptional.absent(), + ConvexOptional customColorValue = const ConvexOptional.absent(), + required String folderPublicId, + ConvexOptional iconCodePoint = const ConvexOptional.absent(), + ConvexOptional iconFontFamily = const ConvexOptional.absent(), + ConvexOptional iconFontPackage = const ConvexOptional.absent(), + ConvexOptional iconId = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), + }) { + final args = encodeFoldersUpdateArgs( + clearCustomColorValue: clearCustomColorValue, + clearIconFontFamily: clearIconFontFamily, + clearIconFontPackage: clearIconFontPackage, + color: color, + customColorValue: customColorValue, + folderPublicId: folderPublicId, + iconCodePoint: iconCodePoint, + iconFontFamily: iconFontFamily, + iconFontPackage: iconFontPackage, + iconId: iconId, + name: name, + ); + return _invoke( + () => _transport.mutation('folders:update', args), + decodeFoldersUpdateResult, + ); + } +} + +abstract interface class HealthModule { + ConvexQuery ping(); +} + +final class _HealthModule implements HealthModule { + const _HealthModule(this._transport); + final ConvexTransport _transport; + @override + ConvexQuery ping() { + final args = encodeHealthPingArgs(); + return ConvexQuery( + transport: _transport, + name: 'health:ping', + args: args, + decode: decodeHealthPingResult, + ); + } +} + +abstract interface class ImagesModule { + Future completeUpload({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + ConvexOptional etag = const ConvexOptional.absent(), + ConvexOptional fileExtension = const ConvexOptional.absent(), + ConvexOptional height = const ConvexOptional.absent(), + ConvexOptional mimeType = const ConvexOptional.absent(), + ConvexOptional objectKey = const ConvexOptional.absent(), + ConvexOptional provider = + const ConvexOptional.absent(), + ConvexOptional storageId = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional uploadId = const ConvexOptional.absent(), + ConvexOptional width = const ConvexOptional.absent(), + }); + Future deleteAssetRef({ + required String assetPublicId, + required String strategyPublicId, + }); + Future generateUploadUrl({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + required String fileExtension, + ConvexOptional height = const ConvexOptional.absent(), + required String mimeType, + required String strategyPublicId, + ConvexOptional width = const ConvexOptional.absent(), + }); + ConvexQuery getAssetUrl({ + required String assetPublicId, + required String strategyPublicId, + }); + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }); +} + +final class _ImagesModule implements ImagesModule { + const _ImagesModule(this._transport); + final ConvexTransport _transport; + @override + Future completeUpload({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + ConvexOptional etag = const ConvexOptional.absent(), + ConvexOptional fileExtension = const ConvexOptional.absent(), + ConvexOptional height = const ConvexOptional.absent(), + ConvexOptional mimeType = const ConvexOptional.absent(), + ConvexOptional objectKey = const ConvexOptional.absent(), + ConvexOptional provider = + const ConvexOptional.absent(), + ConvexOptional storageId = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional uploadId = const ConvexOptional.absent(), + ConvexOptional width = const ConvexOptional.absent(), + }) { + final args = encodeImagesCompleteUploadArgs( + assetPublicId: assetPublicId, + byteSize: byteSize, + etag: etag, + fileExtension: fileExtension, + height: height, + mimeType: mimeType, + objectKey: objectKey, + provider: provider, + storageId: storageId, + strategyPublicId: strategyPublicId, + uploadId: uploadId, + width: width, + ); + return _invoke( + () => _transport.action('images:completeUpload', args), + decodeImagesCompleteUploadResult, + ); + } + + @override + Future deleteAssetRef({ + required String assetPublicId, + required String strategyPublicId, + }) { + final args = encodeImagesDeleteAssetRefArgs( + assetPublicId: assetPublicId, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.action('images:deleteAssetRef', args), + decodeImagesDeleteAssetRefResult, + ); + } + + @override + Future generateUploadUrl({ + required String assetPublicId, + ConvexOptional byteSize = const ConvexOptional.absent(), + required String fileExtension, + ConvexOptional height = const ConvexOptional.absent(), + required String mimeType, + required String strategyPublicId, + ConvexOptional width = const ConvexOptional.absent(), + }) { + final args = encodeImagesGenerateUploadUrlArgs( + assetPublicId: assetPublicId, + byteSize: byteSize, + fileExtension: fileExtension, + height: height, + mimeType: mimeType, + strategyPublicId: strategyPublicId, + width: width, + ); + return _invoke( + () => _transport.action('images:generateUploadUrl', args), + decodeImagesGenerateUploadUrlResult, + ); + } + + @override + ConvexQuery getAssetUrl({ + required String assetPublicId, + required String strategyPublicId, + }) { + final args = encodeImagesGetAssetUrlArgs( + assetPublicId: assetPublicId, + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'images:getAssetUrl', + args: args, + decode: decodeImagesGetAssetUrlResult, + ); + } + + @override + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }) { + final args = encodeImagesListForStrategyArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'images:listForStrategy', + args: args, + decode: decodeImagesListForStrategyResult, + ); + } +} + +abstract interface class InvitesModule { + Future create({ + ConvexOptional expiresAt = const ConvexOptional.absent(), + required InvitesCreateArgsRole role, + required String strategyPublicId, + required String token, + }); + ConvexQuery getValue({ + ConvexOptional strategyPublicId = const ConvexOptional.absent(), + ConvexOptional token = const ConvexOptional.absent(), + }); + Future redeem({required String token}); + Future revoke({ + required String strategyPublicId, + required String token, + }); +} + +final class _InvitesModule implements InvitesModule { + const _InvitesModule(this._transport); + final ConvexTransport _transport; + @override + Future create({ + ConvexOptional expiresAt = const ConvexOptional.absent(), + required InvitesCreateArgsRole role, + required String strategyPublicId, + required String token, + }) { + final args = encodeInvitesCreateArgs( + expiresAt: expiresAt, + role: role, + strategyPublicId: strategyPublicId, + token: token, + ); + return _invoke( + () => _transport.mutation('invites:create', args), + decodeInvitesCreateResult, + ); + } + + @override + ConvexQuery getValue({ + ConvexOptional strategyPublicId = const ConvexOptional.absent(), + ConvexOptional token = const ConvexOptional.absent(), + }) { + final args = encodeInvitesGetArgs( + strategyPublicId: strategyPublicId, + token: token, + ); + return ConvexQuery( + transport: _transport, + name: 'invites:get', + args: args, + decode: decodeInvitesGetResult, + ); + } + + @override + Future redeem({required String token}) { + final args = encodeInvitesRedeemArgs(token: token); + return _invoke( + () => _transport.mutation('invites:redeem', args), + decodeInvitesRedeemResult, + ); + } + + @override + Future revoke({ + required String strategyPublicId, + required String token, + }) { + final args = encodeInvitesRevokeArgs( + strategyPublicId: strategyPublicId, + token: token, + ); + return _invoke( + () => _transport.mutation('invites:revoke', args), + decodeInvitesRevokeResult, + ); + } +} + +abstract interface class LineupsModule { + ConvexQuery> listForPage({ + required String pagePublicId, + required String strategyPublicId, + }); + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }); +} + +final class _LineupsModule implements LineupsModule { + const _LineupsModule(this._transport); + final ConvexTransport _transport; + @override + ConvexQuery> listForPage({ + required String pagePublicId, + required String strategyPublicId, + }) { + final args = encodeLineupsListForPageArgs( + pagePublicId: pagePublicId, + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'lineups:listForPage', + args: args, + decode: decodeLineupsListForPageResult, + ); + } + + @override + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }) { + final args = encodeLineupsListForStrategyArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'lineups:listForStrategy', + args: args, + decode: decodeLineupsListForStrategyResult, + ); + } +} + +abstract interface class OpsModule { + Future applyBatch({ + required String clientId, + required double clientProtocolVersion, + required List ops, + required String strategyPublicId, + }); +} + +final class _OpsModule implements OpsModule { + const _OpsModule(this._transport); + final ConvexTransport _transport; + @override + Future applyBatch({ + required String clientId, + required double clientProtocolVersion, + required List ops, + required String strategyPublicId, + }) { + final args = encodeOpsApplyBatchArgs( + clientId: clientId, + clientProtocolVersion: clientProtocolVersion, + ops: ops, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('ops:applyBatch', args), + decodeOpsApplyBatchResult, + ); + } +} + +abstract interface class PageModule { + ConvexQuery getSnapshot({ + required String pagePublicId, + required String strategyPublicId, + }); +} + +final class _PageModule implements PageModule { + const _PageModule(this._transport); + final ConvexTransport _transport; + @override + ConvexQuery getSnapshot({ + required String pagePublicId, + required String strategyPublicId, + }) { + final args = encodePageGetSnapshotArgs( + pagePublicId: pagePublicId, + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'page:getSnapshot', + args: args, + decode: decodePageGetSnapshotResult, + ); + } +} + +abstract interface class PagesModule { + Future add({ + required double expectedRevision, + required bool isAttack, + required String name, + required String pagePublicId, + ConvexOptional settings = + const ConvexOptional.absent(), + required double sortIndex, + required String strategyPublicId, + }); + Future delete({ + required double expectedRevision, + required String pagePublicId, + required String strategyPublicId, + }); + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }); + Future rename({ + required double expectedRevision, + required String name, + required String pagePublicId, + required String strategyPublicId, + }); + Future reorder({ + required double expectedRevision, + required List orderedPagePublicIds, + required String strategyPublicId, + }); +} + +final class _PagesModule implements PagesModule { + const _PagesModule(this._transport); + final ConvexTransport _transport; + @override + Future add({ + required double expectedRevision, + required bool isAttack, + required String name, + required String pagePublicId, + ConvexOptional settings = + const ConvexOptional.absent(), + required double sortIndex, + required String strategyPublicId, + }) { + final args = encodePagesAddArgs( + expectedRevision: expectedRevision, + isAttack: isAttack, + name: name, + pagePublicId: pagePublicId, + settings: settings, + sortIndex: sortIndex, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('pages:add', args), + decodePagesAddResult, + ); + } + + @override + Future delete({ + required double expectedRevision, + required String pagePublicId, + required String strategyPublicId, + }) { + final args = encodePagesDeleteArgs( + expectedRevision: expectedRevision, + pagePublicId: pagePublicId, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('pages:delete', args), + decodePagesDeleteResult, + ); + } + + @override + ConvexQuery> listForStrategy({ + required String strategyPublicId, + }) { + final args = encodePagesListForStrategyArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'pages:listForStrategy', + args: args, + decode: decodePagesListForStrategyResult, + ); + } + + @override + Future rename({ + required double expectedRevision, + required String name, + required String pagePublicId, + required String strategyPublicId, + }) { + final args = encodePagesRenameArgs( + expectedRevision: expectedRevision, + name: name, + pagePublicId: pagePublicId, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('pages:rename', args), + decodePagesRenameResult, + ); + } + + @override + Future reorder({ + required double expectedRevision, + required List orderedPagePublicIds, + required String strategyPublicId, + }) { + final args = encodePagesReorderArgs( + expectedRevision: expectedRevision, + orderedPagePublicIds: orderedPagePublicIds, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('pages:reorder', args), + decodePagesReorderResult, + ); + } +} + +abstract interface class SharesModule { + Future create({ + required InvitesCreateArgsRole role, + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, + }); + ConvexQuery> list({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + }); + Future redeem({required String token}); + Future revoke({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, + }); +} + +final class _SharesModule implements SharesModule { + const _SharesModule(this._transport); + final ConvexTransport _transport; + @override + Future create({ + required InvitesCreateArgsRole role, + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, + }) { + final args = encodeSharesCreateArgs( + role: role, + targetPublicId: targetPublicId, + targetType: targetType, + token: token, + ); + return _invoke( + () => _transport.mutation('shares:create', args), + decodeSharesCreateResult, + ); + } + + @override + ConvexQuery> list({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + }) { + final args = encodeSharesListArgs( + targetPublicId: targetPublicId, + targetType: targetType, + ); + return ConvexQuery( + transport: _transport, + name: 'shares:list', + args: args, + decode: decodeSharesListResult, + ); + } + + @override + Future redeem({required String token}) { + final args = encodeSharesRedeemArgs(token: token); + return _invoke( + () => _transport.mutation('shares:redeem', args), + decodeSharesRedeemResult, + ); + } + + @override + Future revoke({ + required String targetPublicId, + required SharesCreateArgsTargetType targetType, + required String token, + }) { + final args = encodeSharesRevokeArgs( + targetPublicId: targetPublicId, + targetType: targetType, + token: token, + ); + return _invoke( + () => _transport.mutation('shares:revoke', args), + decodeSharesRevokeResult, + ); + } +} + +abstract interface class StrategiesModule { + Future create({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }); + Future createWithInitialPage({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required bool initialPageIsAttack, + required String initialPageName, + required String initialPagePublicId, + ConvexOptional + initialPageSettings = + const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }); + Future delete({ + required double expectedRevision, + required String strategyPublicId, + }); + ConvexQuery getHeader({ + required String strategyPublicId, + }); + ConvexQuery> listForFolder({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + ConvexOptional scope = + const ConvexOptional.absent(), + }); + ConvexQuery> listSharedWithMe(); + Future move({ + required double expectedRevision, + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String strategyPublicId, + }); + Future update({ + ConvexOptional clearThemeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional clearThemeProfileId = const ConvexOptional.absent(), + required double expectedRevision, + ConvexOptional mapData = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }); +} + +final class _StrategiesModule implements StrategiesModule { + const _StrategiesModule(this._transport); + final ConvexTransport _transport; + @override + Future create({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }) { + final args = encodeStrategiesCreateArgs( + folderPublicId: folderPublicId, + mapData: mapData, + name: name, + publicId: publicId, + themeOverridePalette: themeOverridePalette, + themeProfileId: themeProfileId, + ); + return _invoke( + () => _transport.mutation('strategies:create', args), + decodeStrategiesCreateResult, + ); + } + + @override + Future createWithInitialPage({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required bool initialPageIsAttack, + required String initialPageName, + required String initialPagePublicId, + ConvexOptional + initialPageSettings = + const ConvexOptional.absent(), + required String mapData, + required String name, + required String publicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }) { + final args = encodeStrategiesCreateWithInitialPageArgs( + folderPublicId: folderPublicId, + initialPageIsAttack: initialPageIsAttack, + initialPageName: initialPageName, + initialPagePublicId: initialPagePublicId, + initialPageSettings: initialPageSettings, + mapData: mapData, + name: name, + publicId: publicId, + themeOverridePalette: themeOverridePalette, + themeProfileId: themeProfileId, + ); + return _invoke( + () => _transport.mutation('strategies:createWithInitialPage', args), + decodeStrategiesCreateWithInitialPageResult, + ); + } + + @override + Future delete({ + required double expectedRevision, + required String strategyPublicId, + }) { + final args = encodeStrategiesDeleteArgs( + expectedRevision: expectedRevision, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('strategies:delete', args), + decodeStrategiesDeleteResult, + ); + } + + @override + ConvexQuery getHeader({ + required String strategyPublicId, + }) { + final args = encodeStrategiesGetHeaderArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'strategies:getHeader', + args: args, + decode: decodeStrategiesGetHeaderResult, + ); + } + + @override + ConvexQuery> listForFolder({ + ConvexOptional folderPublicId = const ConvexOptional.absent(), + ConvexOptional scope = + const ConvexOptional.absent(), + }) { + final args = encodeStrategiesListForFolderArgs( + folderPublicId: folderPublicId, + scope: scope, + ); + return ConvexQuery( + transport: _transport, + name: 'strategies:listForFolder', + args: args, + decode: decodeStrategiesListForFolderResult, + ); + } + + @override + ConvexQuery> listSharedWithMe() { + final args = encodeStrategiesListSharedWithMeArgs(); + return ConvexQuery( + transport: _transport, + name: 'strategies:listSharedWithMe', + args: args, + decode: decodeStrategiesListSharedWithMeResult, + ); + } + + @override + Future move({ + required double expectedRevision, + ConvexOptional folderPublicId = const ConvexOptional.absent(), + required String strategyPublicId, + }) { + final args = encodeStrategiesMoveArgs( + expectedRevision: expectedRevision, + folderPublicId: folderPublicId, + strategyPublicId: strategyPublicId, + ); + return _invoke( + () => _transport.mutation('strategies:move', args), + decodeStrategiesMoveResult, + ); + } + + @override + Future update({ + ConvexOptional clearThemeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional clearThemeProfileId = const ConvexOptional.absent(), + required double expectedRevision, + ConvexOptional mapData = const ConvexOptional.absent(), + ConvexOptional name = const ConvexOptional.absent(), + required String strategyPublicId, + ConvexOptional< + OpsApplyBatchArgsOpsItemStrategyPatchPayloadThemeOverridePalette + > + themeOverridePalette = + const ConvexOptional.absent(), + ConvexOptional themeProfileId = const ConvexOptional.absent(), + }) { + final args = encodeStrategiesUpdateArgs( + clearThemeOverridePalette: clearThemeOverridePalette, + clearThemeProfileId: clearThemeProfileId, + expectedRevision: expectedRevision, + mapData: mapData, + name: name, + strategyPublicId: strategyPublicId, + themeOverridePalette: themeOverridePalette, + themeProfileId: themeProfileId, + ); + return _invoke( + () => _transport.mutation('strategies:update', args), + decodeStrategiesUpdateResult, + ); + } +} + +abstract interface class StrategyModule { + ConvexQuery getFullSnapshot({ + required String strategyPublicId, + }); + ConvexQuery getShell({ + required String strategyPublicId, + }); +} + +final class _StrategyModule implements StrategyModule { + const _StrategyModule(this._transport); + final ConvexTransport _transport; + @override + ConvexQuery getFullSnapshot({ + required String strategyPublicId, + }) { + final args = encodeStrategyGetFullSnapshotArgs( + strategyPublicId: strategyPublicId, + ); + return ConvexQuery( + transport: _transport, + name: 'strategy:getFullSnapshot', + args: args, + decode: decodeStrategyGetFullSnapshotResult, + ); + } + + @override + ConvexQuery getShell({ + required String strategyPublicId, + }) { + final args = encodeStrategyGetShellArgs(strategyPublicId: strategyPublicId); + return ConvexQuery( + transport: _transport, + name: 'strategy:getShell', + args: args, + decode: decodeStrategyGetShellResult, + ); + } +} + +abstract interface class UsersModule { + Future ensureCurrentUser(); + ConvexQuery me(); +} + +final class _UsersModule implements UsersModule { + const _UsersModule(this._transport); + final ConvexTransport _transport; + @override + Future ensureCurrentUser() { + final args = encodeUsersEnsureCurrentUserArgs(); + return _invoke( + () => _transport.mutation('users:ensureCurrentUser', args), + decodeUsersEnsureCurrentUserResult, + ); + } + + @override + ConvexQuery me() { + final args = encodeUsersMeArgs(); + return ConvexQuery( + transport: _transport, + name: 'users:me', + args: args, + decode: decodeUsersMeResult, + ); + } +} diff --git a/lib/collab/src/convex_client_native.dart b/lib/collab/src/convex_client_native.dart index d38813b0..a7e5adfa 100644 --- a/lib/collab/src/convex_client_native.dart +++ b/lib/collab/src/convex_client_native.dart @@ -1,9 +1,11 @@ import 'dart:async'; +import 'dart:convert'; +import 'dart:typed_data'; import 'package:convex_flutter/convex_flutter.dart' as native; import 'package:icarus/collab/src/convex_client_types.dart'; -class ConvexClient { +class ConvexClient implements ConvexClientValueSource { ConvexClient._(this._client, this.config); final native.ConvexClient _client; @@ -46,18 +48,40 @@ class ConvexClient { Future query(String name, Map args) => _client.query(name, args); + @override + Future queryValue(String name, Map args) => + _readValue(() => _client.query(name, _convexDartToJson(args))); + Future mutation({ required String name, required Map args, }) => _client.mutation(name: name, args: args); + @override + Future mutationValue({ + required String name, + required Map args, + }) => + _readValue( + () => _client.mutation(name: name, args: _convexDartToJson(args)), + ); + Future action({ required String name, required Map args, }) => _client.action(name: name, args: args); + @override + Future actionValue({ + required String name, + required Map args, + }) => + _readValue( + () => _client.action(name: name, args: _convexDartToJson(args)), + ); + Future subscribe({ required String name, required Map args, @@ -73,6 +97,24 @@ class ConvexClient { return _NativeSubscriptionHandle(handle); } + @override + Future subscribeValue({ + required String name, + required Map args, + required void Function(Object? value) onUpdate, + required void Function(ConvexClientFunctionError error) onError, + }) async { + final handle = await _client.subscribe( + name: name, + args: _convexDartToJson(args), + onUpdate: (value) => onUpdate(_convexJsonToDart(jsonDecode(value))), + onError: (message, value) => onError( + _nativeFunctionError(message: message, encodedData: value), + ), + ); + return _NativeSubscriptionHandle(handle); + } + Future setAuthWithRefresh({ required Future Function() fetchToken, void Function(bool isAuthenticated)? onAuthChange, @@ -90,6 +132,17 @@ class ConvexClient { void dispose() => _client.dispose(); + Future _readValue(Future Function() operation) async { + try { + return _convexJsonToDart(jsonDecode(await operation())); + } on native.ClientError_ConvexError catch (error) { + throw _nativeFunctionError( + message: 'Convex function failed', + encodedData: error.data, + ); + } + } + static WebSocketConnectionState _mapConnectionState( native.WebSocketConnectionState state, ) { @@ -99,6 +152,86 @@ class ConvexClient { } } +ConvexClientFunctionError _nativeFunctionError({ + required String message, + required String? encodedData, +}) { + Object? data; + if (encodedData != null) { + try { + data = _convexJsonToDart(jsonDecode(encodedData)); + } catch (_) { + data = encodedData; + } + } + final dataMap = data is Map ? data : const {}; + final rawCode = dataMap['code']?.toString() ?? 'CONVEX_ERROR'; + final structuredMessage = dataMap['message']; + return ConvexClientFunctionError( + rawCode: rawCode, + message: structuredMessage is String && structuredMessage.isNotEmpty + ? structuredMessage + : message, + data: data, + ); +} + +Map _convexDartToJson(Map value) => + Map.from(_convexValueToJson(value) as Map); + +Object? _convexValueToJson(Object? value) { + if (value is BigInt) { + final minimum = BigInt.parse('-9223372036854775808'); + final maximum = BigInt.parse('9223372036854775807'); + if (value < minimum || value > maximum) { + throw FormatException('Convex int64 is out of range: $value'); + } + final bytes = Uint8List(8); + ByteData.sublistView(bytes).setInt64(0, value.toInt(), Endian.little); + return {r'$integer': base64Encode(bytes)}; + } + if (value is Uint8List) { + return {r'$bytes': base64Encode(value)}; + } + if (value is double && (!value.isFinite || value.isNegative && value == 0)) { + final bytes = Uint8List(8); + ByteData.sublistView(bytes).setFloat64(0, value, Endian.little); + return {r'$float': base64Encode(bytes)}; + } + if (value is List) return value.map(_convexValueToJson).toList(); + if (value is Map) { + return { + for (final entry in value.entries) + entry.key.toString(): _convexValueToJson(entry.value), + }; + } + return value; +} + +Object? _convexJsonToDart(Object? value) { + if (value is List) return value.map(_convexJsonToDart).toList(); + if (value is! Map) return value; + if (value.length == 1 && value[r'$integer'] is String) { + final bytes = base64Decode(value[r'$integer'] as String); + if (bytes.length != 8) throw const FormatException('Invalid Convex int64'); + return BigInt.from(ByteData.sublistView(bytes).getInt64(0, Endian.little)); + } + if (value.length == 1 && value[r'$bytes'] is String) { + return Uint8List.fromList(base64Decode(value[r'$bytes'] as String)); + } + if (value.length == 1 && value[r'$float'] is String) { + final bytes = base64Decode(value[r'$float'] as String); + if (bytes.length != 8) { + throw const FormatException('Invalid Convex float64'); + } + return ByteData.sublistView(bytes).getFloat64(0, Endian.little); + } + return { + for (final entry in value.entries) + entry.key.toString(): _convexJsonToDart(entry.value), + }; +} + class _NativeSubscriptionHandle implements SubscriptionHandle { _NativeSubscriptionHandle(this._handle); diff --git a/lib/collab/src/convex_client_types.dart b/lib/collab/src/convex_client_types.dart index 09d98483..c85b3238 100644 --- a/lib/collab/src/convex_client_types.dart +++ b/lib/collab/src/convex_client_types.dart @@ -1,3 +1,5 @@ +const defaultConvexHealthCheckQuery = 'health:ping'; + class ConvexConfig { const ConvexConfig({ required this.deploymentUrl, @@ -24,3 +26,39 @@ abstract interface class SubscriptionHandle { abstract interface class AuthHandleWrapper { void dispose(); } + +final class ConvexClientFunctionError implements Exception { + const ConvexClientFunctionError({ + required this.rawCode, + required this.message, + required this.data, + }); + + final String rawCode; + final String message; + final Object? data; + + @override + String toString() => 'ConvexClientFunctionError($rawCode, $message)'; +} + +abstract interface class ConvexClientValueSource { + Future queryValue(String name, Map args); + + Future mutationValue({ + required String name, + required Map args, + }); + + Future actionValue({ + required String name, + required Map args, + }); + + Future subscribeValue({ + required String name, + required Map args, + required void Function(Object? value) onUpdate, + required void Function(ConvexClientFunctionError error) onError, + }); +} diff --git a/lib/collab/src/convex_client_web.dart b/lib/collab/src/convex_client_web.dart index 370ae9e6..d9452f24 100644 --- a/lib/collab/src/convex_client_web.dart +++ b/lib/collab/src/convex_client_web.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:js_interop'; import 'dart:js_interop_unsafe'; +import 'dart:typed_data'; import 'package:icarus/collab/src/convex_client_types.dart'; @@ -30,7 +31,29 @@ extension type _JsConnectionState._(JSObject _) implements JSObject { external bool get isWebSocketConnected; } -class ConvexClient { +@JS('BigInt') +external JSBigInt _createJsBigInt(String value); + +@JS('String') +external String _jsString(JSAny? value); + +@JS('Object.keys') +external JSArray _jsObjectKeys(JSObject value); + +extension type _JsUint8ArrayView._(JSObject _) implements JSObject { + external JSArrayBuffer get buffer; +} + +@JS('Uint8Array') +extension type _JsReadableUint8Array._(JSObject _) implements JSObject { + external _JsReadableUint8Array(JSArrayBuffer buffer); + + external int get length; + + external int operator [](int index); +} + +class ConvexClient implements ConvexClientValueSource { ConvexClient._(this._client, this.config) { _setConnectionState( _client.connectionState().isWebSocketConnected @@ -94,6 +117,11 @@ class ConvexClient { return _awaitResult('Query $name', _client.query(name, args.jsify())); } + @override + Future queryValue(String name, Map args) { + return _awaitValue('Query $name', _client.query(name, _toJsConvex(args))); + } + Future mutation({ required String name, required Map args, @@ -104,6 +132,17 @@ class ConvexClient { ); } + @override + Future mutationValue({ + required String name, + required Map args, + }) { + return _awaitValue( + 'Mutation $name', + _client.mutation(name, _toJsConvex(args)), + ); + } + Future action({ required String name, required Map args, @@ -111,6 +150,17 @@ class ConvexClient { return _awaitResult('Action $name', _client.action(name, args.jsify())); } + @override + Future actionValue({ + required String name, + required Map args, + }) { + return _awaitValue( + 'Action $name', + _client.action(name, _toJsConvex(args)), + ); + } + Future subscribe({ required String name, required Map args, @@ -136,6 +186,32 @@ class ConvexClient { ); } + @override + Future subscribeValue({ + required String name, + required Map args, + required void Function(Object? value) onUpdate, + required void Function(ConvexClientFunctionError error) onError, + }) async { + final updateCallback = ((JSAny? value, JSAny? _) { + onUpdate(_fromJsConvex(value)); + }).toJS; + final errorCallback = ((JSAny? error, JSAny? _) { + onError(_jsFunctionError(error)); + }).toJS; + final unsubscribe = _client.onUpdate( + name, + _toJsConvex(args), + updateCallback, + errorCallback, + ); + return _WebSubscriptionHandle( + unsubscribe: unsubscribe, + updateCallback: updateCallback, + errorCallback: errorCallback, + ); + } + Future setAuthWithRefresh({ required Future Function() fetchToken, void Function(bool isAuthenticated)? onAuthChange, @@ -212,6 +288,20 @@ class ConvexClient { } } + Future _awaitValue( + String operation, + JSPromise promise, + ) async { + try { + final result = await promise.toDart.timeout(config.operationTimeout); + return _fromJsConvex(result); + } on TimeoutException { + throw TimeoutException('$operation timed out', config.operationTimeout); + } catch (error) { + throw _jsFunctionError(error); + } + } + void _setConnectionState(WebSocketConnectionState next) { if (_connectionState == next) { return; @@ -244,6 +334,87 @@ class ConvexClient { return 'Unknown Convex error'; } } + + ConvexClientFunctionError _jsFunctionError(Object? error) { + JSAny? jsError; + try { + jsError = error as JSAny?; + } catch (_) { + return ConvexClientFunctionError( + rawCode: 'CONVEX_ERROR', + message: error?.toString() ?? 'Unknown Convex error', + data: null, + ); + } + + Object? data; + String? objectCode; + String message = _jsErrorMessage(jsError); + try { + final object = jsError as JSObject; + final rawData = object.getProperty('data'.toJS); + data = _fromJsConvex(rawData); + final rawObjectCode = object.getProperty('code'.toJS); + objectCode = rawObjectCode == null ? null : _jsString(rawObjectCode); + } catch (_) {} + final dataMap = data is Map ? data : const {}; + final rawCode = dataMap['code']?.toString() ?? objectCode ?? 'CONVEX_ERROR'; + final structuredMessage = dataMap['message']; + if (structuredMessage is String && structuredMessage.isNotEmpty) { + message = structuredMessage; + } + return ConvexClientFunctionError( + rawCode: rawCode, + message: message, + data: data, + ); + } +} + +JSAny? _toJsConvex(Object? value) { + if (value is BigInt) return _createJsBigInt(value.toString()); + if (value is Uint8List) { + final array = Uint8List.fromList(value).toJS; + return _JsUint8ArrayView._(array).buffer; + } + if (value is List) { + return value.map(_toJsConvex).toList(growable: false).toJS; + } + if (value is Map) { + final object = JSObject(); + for (final entry in value.entries) { + object.setProperty(entry.key.toString().toJS, _toJsConvex(entry.value)); + } + return object; + } + return value.jsify(); +} + +Object? _fromJsConvex(JSAny? value) { + if (value == null) return null; + if (value.isA()) return BigInt.parse(_jsString(value)); + if (value.isA()) { + final bytes = _JsReadableUint8Array(value as JSArrayBuffer); + return Uint8List.fromList([ + for (var index = 0; index < bytes.length; index += 1) bytes[index], + ]); + } + if (value.isA>()) { + return (value as JSArray) + .toDart + .map(_fromJsConvex) + .toList(growable: false); + } + if (value.isA()) { + final object = value as JSObject; + return { + for (final key in _jsObjectKeys(object).toDart) + key.toDart: _fromJsConvex( + object.getProperty(key), + ), + }; + } + return value.dartify(); } class _WebSubscriptionHandle implements SubscriptionHandle { diff --git a/lib/collab/transport/convex_transport.dart b/lib/collab/transport/convex_transport.dart new file mode 100644 index 00000000..f3d07200 --- /dev/null +++ b/lib/collab/transport/convex_transport.dart @@ -0,0 +1,144 @@ +import 'dart:typed_data'; + +sealed class ConvexValue { + const ConvexValue(); + + factory ConvexValue.fromDart(Object? value) { + if (value == null) return const ConvexNull(); + if (value is bool) return ConvexBoolean(value); + if (value is int) return ConvexInteger(value); + if (value is BigInt) return ConvexBigInt(value); + if (value is double) return ConvexFloat(value); + if (value is String) return ConvexString(value); + if (value is Uint8List) return ConvexBytes(value); + if (value is List) { + return ConvexArray(value.map(ConvexValue.fromDart).toList()); + } + if (value is Map) { + return ConvexObject({ + for (final entry in value.entries) + entry.key.toString(): ConvexValue.fromDart(entry.value), + }); + } + throw FormatException('Unsupported Convex value ${value.runtimeType}'); + } + + Object? toDart(); +} + +final class ConvexNull extends ConvexValue { + const ConvexNull(); + + @override + Object? toDart() => null; +} + +final class ConvexBoolean extends ConvexValue { + const ConvexBoolean(this.value); + + final bool value; + + @override + bool toDart() => value; +} + +final class ConvexInteger extends ConvexValue { + const ConvexInteger(this.value); + + final int value; + + @override + int toDart() => value; +} + +final class ConvexFloat extends ConvexValue { + const ConvexFloat(this.value); + + final double value; + + @override + double toDart() => value; +} + +final class ConvexBigInt extends ConvexValue { + const ConvexBigInt(this.value); + + final BigInt value; + + @override + BigInt toDart() => value; +} + +final class ConvexString extends ConvexValue { + const ConvexString(this.value); + + final String value; + + @override + String toDart() => value; +} + +final class ConvexBytes extends ConvexValue { + ConvexBytes(Uint8List value) : value = Uint8List.fromList(value); + + final Uint8List value; + + @override + Uint8List toDart() => Uint8List.fromList(value); +} + +final class ConvexArray extends ConvexValue { + ConvexArray(List value) : value = List.unmodifiable(value); + + final List value; + + @override + List toDart() => value.map((item) => item.toDart()).toList(); +} + +final class ConvexObject extends ConvexValue { + ConvexObject(Map value) + : value = Map.unmodifiable(value); + + final Map value; + + @override + Map toDart() => { + for (final entry in value.entries) entry.key: entry.value.toDart(), + }; +} + +final class ConvexTransportError implements Exception { + const ConvexTransportError({ + required this.rawCode, + required this.message, + this.data, + }); + + final String rawCode; + final String message; + final ConvexValue? data; + + @override + String toString() => 'ConvexTransportError($rawCode, $message)'; +} + +final class ConvexNormalizationError implements Exception { + const ConvexNormalizationError(this.path, this.cause); + + final String path; + final Object cause; + + @override + String toString() => 'ConvexNormalizationError($path, $cause)'; +} + +abstract interface class ConvexTransport { + Future query(String name, ConvexObject args); + + Future mutation(String name, ConvexObject args); + + Future action(String name, ConvexObject args); + + Stream subscribe(String name, ConvexObject args); +} diff --git a/lib/collab/transport/convex_transport_adapter.dart b/lib/collab/transport/convex_transport_adapter.dart new file mode 100644 index 00000000..ed496153 --- /dev/null +++ b/lib/collab/transport/convex_transport_adapter.dart @@ -0,0 +1,2 @@ +export 'src/convex_transport_native.dart' + if (dart.library.js_interop) 'src/convex_transport_web.dart'; diff --git a/lib/collab/transport/src/convex_transport_native.dart b/lib/collab/transport/src/convex_transport_native.dart new file mode 100644 index 00000000..9f7343e6 --- /dev/null +++ b/lib/collab/transport/src/convex_transport_native.dart @@ -0,0 +1,5 @@ +import 'package:icarus/collab/transport/src/normalized_convex_transport.dart'; + +final class PlatformConvexTransport extends NormalizedConvexTransport { + const PlatformConvexTransport(super.client); +} diff --git a/lib/collab/transport/src/convex_transport_web.dart b/lib/collab/transport/src/convex_transport_web.dart new file mode 100644 index 00000000..9f7343e6 --- /dev/null +++ b/lib/collab/transport/src/convex_transport_web.dart @@ -0,0 +1,5 @@ +import 'package:icarus/collab/transport/src/normalized_convex_transport.dart'; + +final class PlatformConvexTransport extends NormalizedConvexTransport { + const PlatformConvexTransport(super.client); +} diff --git a/lib/collab/transport/src/normalized_convex_transport.dart b/lib/collab/transport/src/normalized_convex_transport.dart new file mode 100644 index 00000000..55f10814 --- /dev/null +++ b/lib/collab/transport/src/normalized_convex_transport.dart @@ -0,0 +1,119 @@ +import 'dart:async'; + +import 'package:icarus/collab/src/convex_client_types.dart'; +import 'package:icarus/collab/transport/convex_transport.dart'; + +abstract base class NormalizedConvexTransport implements ConvexTransport { + const NormalizedConvexTransport(this.client); + + final ConvexClientValueSource client; + + @override + Future query(String name, ConvexObject args) => _invoke( + '$name.returns', + () => client.queryValue(name, args.toDart()), + ); + + @override + Future mutation(String name, ConvexObject args) => _invoke( + '$name.returns', + () => client.mutationValue(name: name, args: args.toDart()), + ); + + @override + Future action(String name, ConvexObject args) => _invoke( + '$name.returns', + () => client.actionValue(name: name, args: args.toDart()), + ); + + Future _invoke( + String path, + Future Function() operation, + ) async { + try { + return _normalize(await operation(), path); + } on ConvexClientFunctionError catch (error) { + throw _transportError(error); + } + } + + @override + Stream subscribe(String name, ConvexObject args) { + late final StreamController controller; + SubscriptionHandle? handle; + var active = false; + + Future closeForContractFailure( + Object error, StackTrace stackTrace) async { + if (!active) return; + active = false; + controller.addError(error, stackTrace); + handle?.cancel(); + handle = null; + await controller.close(); + } + + Future start() async { + active = true; + try { + final nextHandle = await client.subscribeValue( + name: name, + args: args.toDart(), + onUpdate: (value) { + if (!active) return; + try { + controller.add(_normalize(value, '$name.returns')); + } catch (error, stackTrace) { + closeForContractFailure(error, stackTrace); + } + }, + onError: (error) { + if (!active) return; + controller.addError(_transportError(error)); + }, + ); + if (!active) { + nextHandle.cancel(); + return; + } + handle = nextHandle; + } catch (error, stackTrace) { + await closeForContractFailure(error, stackTrace); + } + } + + controller = StreamController( + onListen: start, + onCancel: () { + active = false; + handle?.cancel(); + handle = null; + }, + ); + return controller.stream; + } + + ConvexValue _normalize(Object? value, String path) { + try { + return ConvexValue.fromDart(value); + } catch (error) { + throw ConvexNormalizationError(path, error); + } + } + + ConvexTransportError _transportError(ConvexClientFunctionError error) { + ConvexValue? data; + if (error.data != null) { + try { + data = ConvexValue.fromDart(error.data); + } catch (_) { + data = ConvexString(error.data.toString()); + } + } + return ConvexTransportError( + rawCode: error.rawCode, + message: error.message, + data: data, + ); + } +} diff --git a/lib/const/json_converters.dart b/lib/const/json_converters.dart index 8bd34473..e07dceb9 100644 --- a/lib/const/json_converters.dart +++ b/lib/const/json_converters.dart @@ -61,9 +61,19 @@ class AbilityInfoConverter @override AbilityInfo fromJson(Map json) { - final info = AgentData.agents[$enumDecode(_agentTypeEnumMap, json["type"])]! - .abilities[json["index"] as int]; - return info; + final type = $enumDecode(_agentTypeEnumMap, json['type']); + final rawIndex = json['index']; + if (rawIndex is! num || + !rawIndex.isFinite || + rawIndex.toInt().toDouble() != rawIndex.toDouble()) { + throw FormatException('Ability index must be an integer: $rawIndex'); + } + final index = rawIndex.toInt(); + final abilities = AgentData.agents[type]?.abilities; + if (abilities == null || index < 0 || index >= abilities.length) { + throw FormatException('Ability index $index is invalid for ${type.name}'); + } + return abilities[index]; } @override diff --git a/lib/const/line_provider.dart b/lib/const/line_provider.dart index f0305bf9..e38fb47f 100644 --- a/lib/const/line_provider.dart +++ b/lib/const/line_provider.dart @@ -374,6 +374,32 @@ class LineUpProvider extends Notifier { updateGroup(group.copyWith(items: items)); } + void updateGroupAgentPosition({ + required String groupId, + required Offset position, + }) { + final group = getGroupById(groupId); + if (group == null) return; + updateGroup( + group.copyWith(agent: group.agent.copyWith(position: position)), + ); + } + + void updateItemAbilityPosition({ + required String groupId, + required String itemId, + required Offset position, + }) { + final item = getItemById(groupId: groupId, itemId: itemId); + if (item == null) return; + updateItem( + groupId: groupId, + item: item.copyWith( + ability: item.ability.copyWith(position: position), + ), + ); + } + void deleteItem({ required String groupId, required String itemId, diff --git a/lib/domain/folder.dart b/lib/domain/folder.dart new file mode 100644 index 00000000..4ceb741f --- /dev/null +++ b/lib/domain/folder.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:hive_ce_flutter/adapters.dart'; +import 'package:icarus/const/folder_icons.dart'; +import 'package:icarus/const/settings.dart'; + +enum FolderColor { + generic, + red, + blue, + green, + orange, + purple, + custom, +} + +class Folder extends HiveObject { + Folder({ + required this.name, + required this.id, + required this.dateCreated, + int? iconId, + IconData? icon, + this.color = FolderColor.red, + this.parentID, + this.customColor, + }) : iconId = iconId ?? + (icon == null + ? FolderIconRegistry.defaultId + : FolderIconRegistry.idForLegacyIconData(icon)); + + String name; + final String id; + final DateTime dateCreated; + String? parentID; + int iconId; + FolderColor color; + Color? customColor; + + static Map folderColorMap = { + FolderColor.red: Colors.red, + FolderColor.blue: Colors.blue, + FolderColor.green: Colors.green, + FolderColor.orange: Colors.orange, + FolderColor.purple: Colors.purple, + FolderColor.generic: Settings.tacticalVioletTheme.card, + }; + + static List folderColors = [ + FolderColor.red, + FolderColor.blue, + FolderColor.green, + FolderColor.orange, + FolderColor.purple, + FolderColor.generic, + ]; + + @Deprecated('Use iconId and FolderIconRegistry instead.') + IconData get icon => FolderIconRegistry.legacyIconDataForId(iconId); + + @Deprecated('Use iconId and FolderIconRegistry instead.') + set icon(IconData icon) { + iconId = FolderIconRegistry.idForLegacyIconData(icon); + } + + @Deprecated('Use FolderIconRegistry.pickerEntries instead.') + static List get folderIcons => [ + for (final entry in FolderIconRegistry.pickerEntries) + if (entry.iconData != null) entry.iconData!, + ]; + + bool get isRoot => parentID == null; +} + +FolderColor folderColorFromWireName(String? value) { + if (value == null) return FolderColor.generic; + return FolderColor.values.firstWhere( + (color) => color.name == value, + orElse: () => FolderColor.generic, + ); +} + +Color? folderCustomColorFromCloud(int? value) { + return value == null ? null : Color(value); +} + +int folderIconIdFromCloud({ + required int? iconId, + required int? codePoint, + required String? fontFamily, + required String? fontPackage, +}) { + if (iconId != null && FolderIconRegistry.isKnownId(iconId)) { + return iconId; + } + final icon = codePoint == null + ? Icons.drive_folder_upload + : IconData( + codePoint, + fontFamily: fontFamily, + fontPackage: fontPackage, + ); + return FolderIconRegistry.idForLegacyIconData(icon); +} diff --git a/lib/main.dart b/lib/main.dart index 26bae03e..df0c96fc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'dart:ui' show PlatformDispatcher; import 'package:app_links/app_links.dart'; import 'package:icarus/collab/convex_client.dart'; +import 'package:icarus/collab/durable_strategy_outbox.dart'; import 'package:custom_mouse_cursor/custom_mouse_cursor.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; @@ -140,6 +141,7 @@ Future main(List args) async { await Hive.openBox(HiveBoxNames.appPreferencesBox); await Hive.openBox(HiveBoxNames.favoriteAgentsBox); await Hive.openBox(HiveBoxNames.strategyOutboxBox); + await prepareDurableStrategyOutbox(); await MapThemeProfilesProvider.bootstrap(); @@ -150,7 +152,7 @@ Future main(List args) async { deploymentUrl: 'https://majestic-eel-413.convex.cloud', clientId: 'dev:majestic-eel-413', operationTimeout: Duration(seconds: 30), - healthCheckQuery: 'health:ping', + healthCheckQuery: defaultConvexHealthCheckQuery, ), ); diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart index 637350da..d298d9fc 100644 --- a/lib/providers/auth_provider.dart +++ b/lib/providers/auth_provider.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:developer'; import 'package:icarus/collab/convex_client.dart'; +import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:icarus/const/app_navigator.dart'; @@ -20,8 +21,6 @@ enum ConvexAuthStatus { incident, } -final RegExp _convexCodeRegex = RegExp(r'"code"\s*:\s*"([A-Z_]+)"'); - const _sensitiveAuthKeys = { 'access_token', 'refresh_token', @@ -31,34 +30,8 @@ const _sensitiveAuthKeys = { 'code_verifier', }; -String? _extractConvexErrorCodeFromText(String text) { - final match = _convexCodeRegex.firstMatch(text); - final code = match?.group(1); - if (code == null || code.isEmpty) { - return null; - } - return code; -} - -bool isConvexUnauthenticatedMessage(String message) { - final normalized = message.toUpperCase(); - final code = _extractConvexErrorCodeFromText(normalized); - if (code == 'UNAUTHENTICATED') { - return true; - } - - return normalized.contains('UNAUTHENTICATED'); -} - bool isConvexUnauthenticatedError(Object error) { - if (error is Map) { - final code = error['code']?.toString().toUpperCase(); - if (code == 'UNAUTHENTICATED') { - return true; - } - } - - return isConvexUnauthenticatedMessage(error.toString()); + return isTypedConvexUnauthenticatedError(error); } String redactAuthUri(Uri uri) { @@ -257,10 +230,7 @@ abstract class AuthProviderConvexApi { String? get currentConnectionStateLabel; Future clearAuth(); Future reconnect(); - Future mutation({ - required String name, - required Map args, - }); + Future ensureCurrentUser(); } abstract class AuthProviderSupabaseApi { @@ -332,11 +302,9 @@ class _DefaultAuthProviderConvexApi implements AuthProviderConvexApi { Future reconnect() => _client.reconnect(); @override - Future mutation({ - required String name, - required Map args, - }) => - _client.mutation(name: name, args: args); + Future ensureCurrentUser() async { + await ConvexStrategyRepository.fromClient(_client).ensureCurrentUser(); + } } class _DefaultAuthProviderSupabaseApi implements AuthProviderSupabaseApi { @@ -1139,7 +1107,7 @@ class AuthProvider extends Notifier { 'Convex auth ready [$trigger] via $readinessSource', name: 'auth', ); - await _convexApi.mutation(name: 'users:ensureCurrentUser', args: {}); + await _convexApi.ensureCurrentUser(); if (!_isAuthContextCurrent( generation: generation, sessionFingerprint: sessionFingerprint, diff --git a/lib/providers/collab/active_page_live_sync_provider.dart b/lib/providers/collab/active_page_live_sync_provider.dart index 63dc57e9..60551c02 100644 --- a/lib/providers/collab/active_page_live_sync_provider.dart +++ b/lib/providers/collab/active_page_live_sync_provider.dart @@ -23,6 +23,8 @@ class ActivePageLiveSyncState { const ActivePageLiveSyncState({ this.strategyPublicId, this.activePageId, + this.hydratedPageId, + this.hydratedEntityKeys = const {}, this.remoteBaseRevisionByEntity = const {}, this.overlayByEntityKey = const {}, this.lastAckBatch = const [], @@ -30,6 +32,8 @@ class ActivePageLiveSyncState { final String? strategyPublicId; final String? activePageId; + final String? hydratedPageId; + final Set hydratedEntityKeys; final Map remoteBaseRevisionByEntity; final Map overlayByEntityKey; final List lastAckBatch; @@ -38,6 +42,9 @@ class ActivePageLiveSyncState { String? strategyPublicId, String? activePageId, bool clearActivePageId = false, + String? hydratedPageId, + bool clearHydratedPage = false, + Set? hydratedEntityKeys, Map? remoteBaseRevisionByEntity, Map? overlayByEntityKey, List? lastAckBatch, @@ -46,6 +53,9 @@ class ActivePageLiveSyncState { strategyPublicId: strategyPublicId ?? this.strategyPublicId, activePageId: clearActivePageId ? null : (activePageId ?? this.activePageId), + hydratedPageId: + clearHydratedPage ? null : (hydratedPageId ?? this.hydratedPageId), + hydratedEntityKeys: hydratedEntityKeys ?? this.hydratedEntityKeys, remoteBaseRevisionByEntity: remoteBaseRevisionByEntity ?? this.remoteBaseRevisionByEntity, overlayByEntityKey: overlayByEntityKey ?? this.overlayByEntityKey, @@ -77,10 +87,15 @@ class ActivePageLiveSyncNotifier extends Notifier { required String? strategyPublicId, required String? activePageId, }) { + final contextChanged = strategyPublicId != state.strategyPublicId || + activePageId != state.activePageId; state = state.copyWith( strategyPublicId: strategyPublicId, activePageId: activePageId, clearActivePageId: activePageId == null, + clearHydratedPage: contextChanged, + hydratedEntityKeys: + contextChanged ? const {} : state.hydratedEntityKeys, remoteBaseRevisionByEntity: strategyPublicId == state.strategyPublicId ? state.remoteBaseRevisionByEntity : const {}, @@ -90,6 +105,28 @@ class ActivePageLiveSyncNotifier extends Notifier { ); } + void markPageUnhydrated({ + required String strategyPublicId, + required String pageId, + }) { + setContext(strategyPublicId: strategyPublicId, activePageId: pageId); + state = state.copyWith( + clearHydratedPage: true, + hydratedEntityKeys: const {}, + ); + } + + void markPageHydrated({ + required String strategyPublicId, + required String pageId, + }) { + setContext(strategyPublicId: strategyPublicId, activePageId: pageId); + state = state.copyWith( + hydratedPageId: pageId, + hydratedEntityKeys: _normalizedLocalEntities(pageId).keys.toSet(), + ); + } + bool hasOverlayForPage(String pageId) { return state.overlayByEntityKey.keys.any((key) => key.pageId == pageId); } @@ -103,6 +140,10 @@ class ActivePageLiveSyncNotifier extends Notifier { required String pageId, }) { setContext(strategyPublicId: strategyPublicId, activePageId: pageId); + if (state.hydratedPageId != pageId) { + _debugLog('sync.skip page=$pageId reason=page_not_hydrated'); + return null; + } final snapshot = ref.read(remoteEditorSnapshotProvider).valueOrNull; final remotePage = snapshot?.activePage; if (snapshot == null || @@ -181,6 +222,15 @@ class ActivePageLiveSyncNotifier extends Notifier { } if (local == null && remote != null) { + final wasHydratedLocally = state.hydratedEntityKeys.contains(key); + if (!wasHydratedLocally && + existingOverlay == null && + !shouldPreserveTouched) { + _debugLog( + 'overlay.skip $key reason=remote_not_yet_hydrated_locally', + ); + continue; + } final overlay = ActivePageOverlayEntry( entityKey: key, entityType: remote.overlayEntityType, @@ -602,75 +652,84 @@ class ActivePageLiveSyncNotifier extends Notifier { final entityId = overlay.entityKey.entityId; switch (overlay.entityType) { case ActivePageOverlayEntityType.pageDescriptor: - return StrategyOp( + return PagePatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.page, - entityPublicId: pageId, - payload: overlay.desiredPayload, - expectedRevision: remote?.revision ?? overlay.baseRevision, + pagePublicId: pageId, + payload: Map.from(overlay.desiredPayload as Map), + expectedPageRevision: remote?.revision ?? overlay.baseRevision, ); case ActivePageOverlayEntityType.pageContent: - return StrategyOp( + final payload = Map.from( + overlay.desiredPayload as Map, + ); + return PageContentPatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.pageContent, - entityPublicId: pageId, - payload: overlay.desiredPayload, - expectedRevision: remote?.revision ?? overlay.baseRevision, + pagePublicId: pageId, + settings: Map.from(payload['settings'] as Map), + expectedPageContentRevision: remote?.revision ?? overlay.baseRevision, ); case ActivePageOverlayEntityType.element: if (entityId == null) { return null; } if (overlay.deletion) { - return StrategyOp( + return ElementDeleteOp( opId: const Uuid().v4(), - kind: StrategyOpKind.delete, - entityType: StrategyOpEntityType.element, - entityPublicId: entityId, + elementPublicId: entityId, pagePublicId: pageId, - expectedRevision: remote?.revision ?? overlay.baseRevision, + expectedElementRevision: remote?.revision ?? overlay.baseRevision, ); } - return StrategyOp( - opId: const Uuid().v4(), - kind: remote == null || remote.deleted - ? StrategyOpKind.add - : StrategyOpKind.patch, - entityType: StrategyOpEntityType.element, - entityPublicId: entityId, - pagePublicId: pageId, - payload: overlay.desiredPayload, - sortIndex: overlay.desiredSortIndex, - expectedRevision: remote?.revision, - ); + final payload = + Map.from(overlay.desiredPayload as Map); + return remote == null || remote.deleted + ? ElementAddOp( + opId: const Uuid().v4(), + elementPublicId: entityId, + pagePublicId: pageId, + payload: payload, + sortIndex: overlay.desiredSortIndex ?? 0, + expectedElementRevision: remote?.revision, + ) + : ElementPatchOp( + opId: const Uuid().v4(), + elementPublicId: entityId, + pagePublicId: pageId, + payload: payload, + sortIndex: overlay.desiredSortIndex, + expectedElementRevision: remote.revision, + ); case ActivePageOverlayEntityType.lineup: if (entityId == null) { return null; } if (overlay.deletion) { - return StrategyOp( + return LineupDeleteOp( opId: const Uuid().v4(), - kind: StrategyOpKind.delete, - entityType: StrategyOpEntityType.lineup, - entityPublicId: entityId, + lineupPublicId: entityId, pagePublicId: pageId, - expectedRevision: remote?.revision ?? overlay.baseRevision, + expectedLineupRevision: remote?.revision ?? overlay.baseRevision, ); } - return StrategyOp( - opId: const Uuid().v4(), - kind: remote == null || remote.deleted - ? StrategyOpKind.add - : StrategyOpKind.patch, - entityType: StrategyOpEntityType.lineup, - entityPublicId: entityId, - pagePublicId: pageId, - payload: overlay.desiredPayload, - sortIndex: overlay.desiredSortIndex, - expectedRevision: remote?.revision, - ); + final payload = + Map.from(overlay.desiredPayload as Map); + return remote == null || remote.deleted + ? LineupAddOp( + opId: const Uuid().v4(), + lineupPublicId: entityId, + pagePublicId: pageId, + payload: payload, + sortIndex: overlay.desiredSortIndex ?? 0, + expectedLineupRevision: remote?.revision, + ) + : LineupPatchOp( + opId: const Uuid().v4(), + lineupPublicId: entityId, + pagePublicId: pageId, + payload: payload, + sortIndex: overlay.desiredSortIndex, + expectedLineupRevision: remote.revision, + ); } } diff --git a/lib/providers/collab/cloud_migration_provider.dart b/lib/providers/collab/cloud_migration_provider.dart index 0a47261d..1a7cf4db 100644 --- a/lib/providers/collab/cloud_migration_provider.dart +++ b/lib/providers/collab/cloud_migration_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:developer'; -import 'package:icarus/collab/convex_client.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_ce_flutter/adapters.dart'; import 'package:icarus/collab/collab_models.dart'; @@ -120,15 +119,15 @@ class _DefaultCloudMigrationApi implements CloudMigrationApi { required Map settings, required int expectedRevision, }) async { - await ConvexClient.instance.mutation(name: 'pages:add', args: { - 'strategyPublicId': strategyPublicId, - 'pagePublicId': pagePublicId, - 'name': name, - 'sortIndex': sortIndex, - 'isAttack': isAttack, - 'settings': settings, - 'expectedRevision': expectedRevision, - }); + await _repository.addPage( + strategyPublicId: strategyPublicId, + pagePublicId: pagePublicId, + name: name, + sortIndex: sortIndex, + isAttack: isAttack, + settings: settings, + expectedRevision: expectedRevision, + ); } @override diff --git a/lib/providers/collab/remote_library_provider.dart b/lib/providers/collab/remote_library_provider.dart index 80ba3b4b..490ba213 100644 --- a/lib/providers/collab/remote_library_provider.dart +++ b/lib/providers/collab/remote_library_provider.dart @@ -1,61 +1,84 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:icarus/providers/auth_provider.dart'; import 'package:icarus/providers/collab/cloud_collab_provider.dart'; import 'package:icarus/providers/folder_provider.dart'; import 'package:icarus/providers/library_workspace_provider.dart'; -final cloudFoldersProvider = - StreamProvider.autoDispose>((ref) async* { +final cloudFolderTreeProvider = + StreamProvider.autoDispose>((ref) async* { final isCloud = ref.watch(isCloudCollabEnabledProvider); final auth = ref.watch(authProvider); if (!isCloud || auth.hasActiveAuthIncident) { - yield const []; + yield const []; return; } - final section = ref.watch(cloudLibrarySectionProvider); - final parentFolderId = ref.watch(folderProvider); final repo = ref.watch(convexStrategyRepositoryProvider); try { - await for (final folders in repo.watchFoldersForParent( - parentFolderId, - scope: section == CloudLibrarySection.sharedWithMe ? 'shared' : 'owned', - )) { + await for (final folders in repo.watchAllFolders()) { yield folders; } } catch (error, stackTrace) { - if (_isInvalidFolderError(error)) { - ref - .read(folderProvider.notifier) - .updateWorkspaceFolderId(LibraryWorkspace.cloud, null); - yield const []; - return; - } if (isConvexUnauthenticatedError(error)) { unawaited( ref.read(authProvider.notifier).reportConvexUnauthenticated( - source: 'remote_library:folders', + source: 'remote_library:folder_tree', error: error, stackTrace: stackTrace, ), ); - yield const []; + yield const []; return; } rethrow; } }); +// This is the same cached provider, retained for the widgets whose concern is +// the complete tree rather than the current folder's children. +final cloudAllFoldersProvider = cloudFolderTreeProvider; + +final cloudFoldersProvider = + StreamProvider.autoDispose>((ref) async* { + final section = ref.watch(cloudLibrarySectionProvider); + final parentFolderId = ref.watch(folderProvider); + final tree = ref.watch(cloudFolderTreeProvider); + final allFolders = switch (tree) { + AsyncData(:final value) => value, + AsyncError(:final error, :final stackTrace) => + Error.throwWithStackTrace(error, stackTrace), + _ => null, + }; + if (allFolders == null) return; + + final wantsShared = section == CloudLibrarySection.sharedWithMe; + final scopedFolders = allFolders + .where((entry) => + wantsShared ? entry.role != 'owner' : entry.role == 'owner') + .toList(growable: false); + if (parentFolderId != null && + !scopedFolders.any((entry) => entry.folder.id == parentFolderId)) { + ref + .read(folderProvider.notifier) + .updateWorkspaceFolderId(LibraryWorkspace.cloud, null); + yield const []; + return; + } + yield scopedFolders + .where((entry) => entry.folder.parentID == parentFolderId) + .toList(growable: false); +}); + final cloudStrategiesProvider = - StreamProvider.autoDispose>((ref) async* { + StreamProvider.autoDispose>((ref) async* { final isCloud = ref.watch(isCloudCollabEnabledProvider); final auth = ref.watch(authProvider); if (!isCloud || auth.hasActiveAuthIncident) { - yield const []; + yield const []; return; } @@ -77,7 +100,7 @@ final cloudStrategiesProvider = ref .read(folderProvider.notifier) .updateWorkspaceFolderId(LibraryWorkspace.cloud, null); - yield const []; + yield const []; return; } if (isConvexUnauthenticatedError(error)) { @@ -88,37 +111,7 @@ final cloudStrategiesProvider = stackTrace: stackTrace, ), ); - yield const []; - return; - } - rethrow; - } -}); - -final cloudAllFoldersProvider = - StreamProvider.autoDispose>((ref) async* { - final isCloud = ref.watch(isCloudCollabEnabledProvider); - final auth = ref.watch(authProvider); - if (!isCloud || auth.hasActiveAuthIncident) { - yield const []; - return; - } - - final repo = ref.watch(convexStrategyRepositoryProvider); - try { - await for (final folders in repo.watchAllFolders()) { - yield folders; - } - } catch (error, stackTrace) { - if (isConvexUnauthenticatedError(error)) { - unawaited( - ref.read(authProvider.notifier).reportConvexUnauthenticated( - source: 'remote_library:all_folders', - error: error, - stackTrace: stackTrace, - ), - ); - yield const []; + yield const []; return; } rethrow; diff --git a/lib/providers/collab/remote_strategy_snapshot_provider.dart b/lib/providers/collab/remote_strategy_snapshot_provider.dart index 611bd870..3d9859c5 100644 --- a/lib/providers/collab/remote_strategy_snapshot_provider.dart +++ b/lib/providers/collab/remote_strategy_snapshot_provider.dart @@ -267,7 +267,7 @@ class RemoteEditorSnapshotNotifier required Object error, StackTrace? stackTrace, }) { - if (isConvexUnauthenticatedMessage(error.toString())) { + if (isConvexUnauthenticatedError(error)) { unawaited(ref.read(authProvider.notifier).reportConvexUnauthenticated( source: source, error: error, diff --git a/lib/providers/collab/strategy_op_queue_provider.dart b/lib/providers/collab/strategy_op_queue_provider.dart index 098abd3e..f8fba8f7 100644 --- a/lib/providers/collab/strategy_op_queue_provider.dart +++ b/lib/providers/collab/strategy_op_queue_provider.dart @@ -455,26 +455,14 @@ class StrategyOpQueueNotifier extends Notifier { final retryRevision = record?.latestServerRevision ?? rejectedOp.expectedRevision; if (retryRevision == null) continue; - final isTombstoneRestore = rejectedOp.kind == StrategyOpKind.add && - (rejectedOp.entityType == StrategyOpEntityType.element || - rejectedOp.entityType == StrategyOpEntityType.lineup) && - (record?.lastError == 'missing_expected_revision' || - record?.lastError == 'revision_mismatch'); - final rebasedKind = !isTombstoneRestore && - rejectedOp.kind == StrategyOpKind.add && - (rejectedOp.entityType == StrategyOpEntityType.element || - rejectedOp.entityType == StrategyOpEntityType.lineup) - ? StrategyOpKind.patch - : rejectedOp.kind; - final rebasedOp = StrategyOp( - opId: const Uuid().v4(), - kind: rebasedKind, - entityType: rejectedOp.entityType, - entityPublicId: rejectedOp.entityPublicId, - pagePublicId: rejectedOp.pagePublicId, - payload: rejectedOp.payload, - sortIndex: rejectedOp.sortIndex, - expectedRevision: retryRevision, + final isTombstoneRestore = + (rejectedOp is ElementAddOp || rejectedOp is LineupAddOp) && + (record?.lastError == 'missing_expected_revision' || + record?.lastError == 'revision_mismatch'); + final rebasedOp = _rebaseRejectedOp( + rejectedOp, + retryRevision, + preserveAdd: isTombstoneRestore, ); final pending = PendingOp( op: rebasedOp, @@ -844,31 +832,250 @@ class StrategyOpQueueNotifier extends Notifier { } StrategyOp? _mergeQueuedIntent(StrategyOp existing, StrategyOp desired) { - if (desired.kind == StrategyOpKind.delete && - existing.kind == StrategyOpKind.add) return null; - if (existing.kind == StrategyOpKind.add && - desired.kind == StrategyOpKind.patch) { - return StrategyOp( - opId: existing.opId, - kind: StrategyOpKind.add, - entityType: existing.entityType, - entityPublicId: existing.entityPublicId, - pagePublicId: existing.pagePublicId, - payload: desired.payload ?? existing.payload, - sortIndex: desired.sortIndex ?? existing.sortIndex, - expectedRevision: existing.expectedRevision, - ); + if ((existing is PageAddOp && desired is PageDeleteOp) || + (existing is ElementAddOp && desired is ElementDeleteOp) || + (existing is LineupAddOp && desired is LineupDeleteOp)) { + return null; } - return StrategyOp( - opId: existing.opId, - kind: desired.kind, - entityType: desired.entityType, - entityPublicId: desired.entityPublicId ?? existing.entityPublicId, - pagePublicId: desired.pagePublicId ?? existing.pagePublicId, - payload: desired.payload ?? existing.payload, - sortIndex: desired.sortIndex ?? existing.sortIndex, - expectedRevision: desired.expectedRevision ?? existing.expectedRevision, - ); + final replacementOpId = const Uuid().v4(); + if (existing case PageAddOp()) { + if (desired case PagePatchOp()) { + return PageAddOp( + opId: replacementOpId, + pagePublicId: existing.pagePublicId, + payload: {...existing.payload, ...desired.payload}, + sortIndex: existing.sortIndex, + expectedStrategyRevision: existing.expectedStrategyRevision, + ); + } + } + if (existing case ElementAddOp()) { + if (desired case ElementPatchOp()) { + return ElementAddOp( + opId: replacementOpId, + elementPublicId: existing.elementPublicId, + pagePublicId: desired.pagePublicId ?? existing.pagePublicId, + payload: desired.payload ?? existing.payload, + sortIndex: desired.sortIndex ?? existing.sortIndex, + expectedElementRevision: existing.expectedElementRevision, + ); + } + } + if (existing case LineupAddOp()) { + if (desired case LineupPatchOp()) { + return LineupAddOp( + opId: replacementOpId, + lineupPublicId: existing.lineupPublicId, + pagePublicId: desired.pagePublicId ?? existing.pagePublicId, + payload: desired.payload ?? existing.payload, + sortIndex: desired.sortIndex ?? existing.sortIndex, + expectedLineupRevision: existing.expectedLineupRevision, + ); + } + } + if (existing case StrategyPatchOp()) { + if (desired case StrategyPatchOp()) { + return StrategyPatchOp( + opId: replacementOpId, + payload: {...existing.payload, ...desired.payload}, + expectedStrategyRevision: desired.expectedStrategyRevision, + ); + } + } + if (existing case PagePatchOp()) { + if (desired case PagePatchOp()) { + return PagePatchOp( + opId: replacementOpId, + pagePublicId: desired.pagePublicId, + payload: {...existing.payload, ...desired.payload}, + expectedPageRevision: desired.expectedPageRevision, + ); + } + } + if (existing case ElementPatchOp()) { + if (desired case ElementPatchOp()) { + return ElementPatchOp( + opId: replacementOpId, + elementPublicId: desired.elementPublicId, + pagePublicId: desired.pagePublicId ?? existing.pagePublicId, + payload: desired.payload ?? existing.payload, + sortIndex: desired.sortIndex ?? existing.sortIndex, + expectedElementRevision: desired.expectedElementRevision, + ); + } + } + if (existing case LineupPatchOp()) { + if (desired case LineupPatchOp()) { + return LineupPatchOp( + opId: replacementOpId, + lineupPublicId: desired.lineupPublicId, + pagePublicId: desired.pagePublicId ?? existing.pagePublicId, + payload: desired.payload ?? existing.payload, + sortIndex: desired.sortIndex ?? existing.sortIndex, + expectedLineupRevision: desired.expectedLineupRevision, + ); + } + } + return desired.withOpId(replacementOpId); + } + + StrategyOp _rebaseRejectedOp( + StrategyOp op, + int revision, { + required bool preserveAdd, + }) { + final opId = const Uuid().v4(); + return switch (op) { + StrategyPatchOp(:final payload) => StrategyPatchOp( + opId: opId, + payload: payload, + expectedStrategyRevision: revision, + ), + PageAddOp(:final pagePublicId, :final payload, :final sortIndex) => + PageAddOp( + opId: opId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedStrategyRevision: revision, + ), + PagePatchOp(:final pagePublicId, :final payload) => PagePatchOp( + opId: opId, + pagePublicId: pagePublicId, + payload: payload, + expectedPageRevision: revision, + ), + PageDeleteOp(:final pagePublicId) => PageDeleteOp( + opId: opId, + pagePublicId: pagePublicId, + expectedStrategyRevision: revision, + ), + PageReorderOp(:final pagePublicId, :final sortIndex) => PageReorderOp( + opId: opId, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedStrategyRevision: revision, + ), + PageContentPatchOp(:final pagePublicId, :final settings) => + PageContentPatchOp( + opId: opId, + pagePublicId: pagePublicId, + settings: settings, + expectedPageContentRevision: revision, + ), + ElementAddOp( + :final elementPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) => + preserveAdd + ? ElementAddOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: revision, + ) + : ElementPatchOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: revision, + ), + ElementPatchOp( + :final elementPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) => + ElementPatchOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedElementRevision: revision, + ), + ElementDeleteOp(:final elementPublicId, :final pagePublicId) => + ElementDeleteOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + expectedElementRevision: revision, + ), + ElementReorderOp( + :final elementPublicId, + :final pagePublicId, + :final sortIndex, + ) => + ElementReorderOp( + opId: opId, + elementPublicId: elementPublicId, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedElementRevision: revision, + ), + LineupAddOp( + :final lineupPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) => + preserveAdd + ? LineupAddOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: revision, + ) + : LineupPatchOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: revision, + ), + LineupPatchOp( + :final lineupPublicId, + :final pagePublicId, + :final payload, + :final sortIndex, + ) => + LineupPatchOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + payload: payload, + sortIndex: sortIndex, + expectedLineupRevision: revision, + ), + LineupDeleteOp(:final lineupPublicId, :final pagePublicId) => + LineupDeleteOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + expectedLineupRevision: revision, + ), + LineupReorderOp( + :final lineupPublicId, + :final pagePublicId, + :final sortIndex, + ) => + LineupReorderOp( + opId: opId, + lineupPublicId: lineupPublicId, + pagePublicId: pagePublicId, + sortIndex: sortIndex, + expectedLineupRevision: revision, + ), + }; } String? _loadedAttentionMessage({ diff --git a/lib/providers/folder_provider.dart b/lib/providers/folder_provider.dart index 0810e586..9582e972 100644 --- a/lib/providers/folder_provider.dart +++ b/lib/providers/folder_provider.dart @@ -1,12 +1,12 @@ -import 'package:icarus/collab/convex_client.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_ce_flutter/adapters.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/collab/convex_strategy_repository.dart'; import 'package:icarus/const/folder_icons.dart'; import 'package:icarus/const/hive_boxes.dart'; import 'package:icarus/const/settings.dart'; +import 'package:icarus/domain/folder.dart'; import 'package:icarus/providers/auth_provider.dart'; import 'package:icarus/providers/collab/remote_library_provider.dart'; import 'package:icarus/providers/library_workspace_provider.dart'; @@ -15,73 +15,7 @@ import 'package:icarus/strategy/strategy_models.dart'; import 'package:icarus/strategy/strategy_page_models.dart'; import 'package:uuid/uuid.dart'; -enum FolderColor { - generic, - red, - blue, - green, - orange, - purple, - custom, -} - -class Folder extends HiveObject { - String name; - final String id; - final DateTime dateCreated; - String? parentID; // null for root folders, clearer than empty string - int iconId; - FolderColor color; - Color? customColor; - - Folder({ - required this.name, - required this.id, - required this.dateCreated, - int? iconId, - IconData? icon, - this.color = FolderColor.red, - this.parentID, // Optional, defaults to null (root) - this.customColor, - }) : iconId = iconId ?? - (icon == null - ? FolderIconRegistry.defaultId - : FolderIconRegistry.idForLegacyIconData(icon)); - - static Map folderColorMap = { - FolderColor.red: Colors.red, - FolderColor.blue: Colors.blue, - FolderColor.green: Colors.green, - FolderColor.orange: Colors.orange, - FolderColor.purple: Colors.purple, - FolderColor.generic: Settings.tacticalVioletTheme.card, - }; - - static List folderColors = [ - FolderColor.red, - FolderColor.blue, - FolderColor.green, - FolderColor.orange, - FolderColor.purple, - FolderColor.generic, - ]; - - @Deprecated('Use iconId and FolderIconRegistry instead.') - IconData get icon => FolderIconRegistry.legacyIconDataForId(iconId); - - @Deprecated('Use iconId and FolderIconRegistry instead.') - set icon(IconData icon) { - iconId = FolderIconRegistry.idForLegacyIconData(icon); - } - - @Deprecated('Use FolderIconRegistry.pickerEntries instead.') - static List get folderIcons => [ - for (final entry in FolderIconRegistry.pickerEntries) - if (entry.iconData != null) entry.iconData!, - ]; - - bool get isRoot => parentID == null; -} +export 'package:icarus/domain/folder.dart' show Folder, FolderColor; final folderProvider = NotifierProvider(FolderProvider.new); @@ -90,55 +24,6 @@ class FolderProvider extends Notifier { String? _localCurrentFolderId; String? _cloudCurrentFolderId; - static FolderColor decodeFolderColor(String? raw) { - if (raw == null) { - return FolderColor.generic; - } - for (final value in FolderColor.values) { - if (value.name == raw) { - return value; - } - } - return FolderColor.generic; - } - - static IconData decodeFolderIcon( - CloudFolderSummary folder, { - IconData fallback = Icons.drive_folder_upload, - }) { - final codePoint = folder.iconCodePoint; - if (codePoint == null) { - return fallback; - } - return IconData( - codePoint, - fontFamily: folder.iconFontFamily, - fontPackage: folder.iconFontPackage, - ); - } - - static int decodeFolderIconId(CloudFolderSummary folder) { - final iconId = folder.iconId; - if (iconId != null && FolderIconRegistry.isKnownId(iconId)) { - return iconId; - } - return FolderIconRegistry.idForLegacyIconData(decodeFolderIcon(folder)); - } - - static Folder cloudSummaryToFolder(CloudFolderSummary folder) { - return Folder( - name: folder.name, - id: folder.publicId, - dateCreated: folder.createdAt, - iconId: decodeFolderIconId(folder), - color: decodeFolderColor(folder.color), - parentID: folder.parentFolderPublicId, - customColor: folder.customColorValue == null - ? null - : Color(folder.customColorValue!), - ); - } - Future createFolder({ required String name, required int iconId, @@ -250,11 +135,11 @@ class FolderProvider extends Notifier { Folder? findCloudFolderByID( String id, - Iterable cloudFolders, + Iterable cloudFolders, ) { return cloudFolders - .where((folder) => folder.publicId == id) - .map(cloudSummaryToFolder) + .where((entry) => entry.folder.id == id) + .map((entry) => entry.folder) .firstOrNull; } @@ -265,9 +150,7 @@ class FolderProvider extends Notifier { final targetWorkspace = workspace ?? _currentWorkspace; if (targetWorkspace == LibraryWorkspace.cloud) { try { - await ConvexClient.instance.mutation(name: 'folders:delete', args: { - 'folderPublicId': folderID, - }); + await ref.read(convexStrategyRepositoryProvider).deleteFolder(folderID); } catch (error, stackTrace) { await _maybeReportCloudUnauthenticated( source: 'folder:delete', @@ -315,22 +198,19 @@ class FolderProvider extends Notifier { final iconFontFamily = newIcon?.fontFamily; final iconFontPackage = newIcon?.fontPackage; try { - final args = { - 'folderPublicId': folder.id, - 'name': newName, - 'iconId': newIconId, - if (newIcon != null) 'iconCodePoint': newIcon.codePoint, - if (iconFontFamily != null) 'iconFontFamily': iconFontFamily, - if (iconFontFamily == null) 'clearIconFontFamily': true, - if (iconFontPackage != null) 'iconFontPackage': iconFontPackage, - if (iconFontPackage == null) 'clearIconFontPackage': true, - 'color': newColor.name, - if (newCustomColor != null) - 'customColorValue': newCustomColor.toARGB32(), - if (newCustomColor == null) 'clearCustomColorValue': true, - }; - await ConvexClient.instance - .mutation(name: 'folders:update', args: args); + await ref.read(convexStrategyRepositoryProvider).updateFolder( + folderPublicId: folder.id, + name: newName, + iconId: newIconId, + iconCodePoint: newIcon?.codePoint, + iconFontFamily: iconFontFamily, + clearIconFontFamily: iconFontFamily == null, + iconFontPackage: iconFontPackage, + clearIconFontPackage: iconFontPackage == null, + color: newColor.name, + customColorValue: newCustomColor?.toARGB32(), + clearCustomColorValue: newCustomColor == null, + ); ref.invalidate(cloudFoldersProvider); ref.invalidate(cloudAllFoldersProvider); } catch (error, stackTrace) { @@ -358,10 +238,10 @@ class FolderProvider extends Notifier { final targetWorkspace = workspace ?? _currentWorkspace; if (targetWorkspace == LibraryWorkspace.cloud) { try { - await ConvexClient.instance.mutation(name: 'folders:move', args: { - 'folderPublicId': folderID, - if (parentID != null) 'parentFolderPublicId': parentID, - }); + await ref.read(convexStrategyRepositoryProvider).moveFolder( + folderPublicId: folderID, + parentFolderPublicId: parentID, + ); } catch (error, stackTrace) { await _maybeReportCloudUnauthenticated( source: 'folder:move', diff --git a/lib/providers/share_link_provider.dart b/lib/providers/share_link_provider.dart index d7d4bba2..f934f7e8 100644 --- a/lib/providers/share_link_provider.dart +++ b/lib/providers/share_link_provider.dart @@ -65,12 +65,11 @@ class ShareLinkController extends Notifier { .select(CloudLibrarySection.sharedWithMe); ref.read(folderProvider.notifier).updateWorkspaceFolderId( LibraryWorkspace.cloud, - response['folderPublicId'] as String?, + response.folderPublicId, ); - final targetType = response['targetType'] as String? ?? 'item'; Settings.showToast( - message: targetType == 'folder' + message: response.targetType == 'folder' ? 'Shared folder added to your library.' : 'Shared strategy added to your library.', backgroundColor: Settings.tacticalVioletTheme.primary, diff --git a/lib/providers/strategy_page_session_provider.dart b/lib/providers/strategy_page_session_provider.dart index 8b1dc2cd..fa110a8a 100644 --- a/lib/providers/strategy_page_session_provider.dart +++ b/lib/providers/strategy_page_session_provider.dart @@ -269,6 +269,13 @@ class StrategyPageSessionNotifier extends Notifier { await ref .read(remoteEditorSnapshotProvider.notifier) .setActivePage(previousPageId); + final strategyId = strategyState.strategyId; + if (strategyId != null && previousPageId != null) { + ref.read(activePageLiveSyncProvider.notifier).markPageHydrated( + strategyPublicId: strategyId, + pageId: previousPageId, + ); + } } catch (_) { // Preserve the original switch failure; the live read can recover // independently without leaving the transition state stuck. @@ -420,6 +427,12 @@ class StrategyPageSessionNotifier extends Notifier { strategyPublicId: strategyId, activePageId: pageId, ); + if (source == StrategySource.cloud) { + ref.read(activePageLiveSyncProvider.notifier).markPageUnhydrated( + strategyPublicId: strategyId, + pageId: pageId, + ); + } final pageData = await _resolvePageSource(strategyId, source).loadPage(pageId); await _applyLoadedPageData( @@ -458,6 +471,12 @@ class StrategyPageSessionNotifier extends Notifier { themeOverridePalette: themeOverridePalette, preserveHistory: preserveHistory, ); + if (source == StrategySource.cloud) { + ref.read(activePageLiveSyncProvider.notifier).markPageHydrated( + strategyPublicId: strategyId, + pageId: pageData.pageId, + ); + } _updateHydrationBookkeeping( pageData.pageId, hydrationKey: hydrationKey, diff --git a/lib/providers/strategy_provider.dart b/lib/providers/strategy_provider.dart index b9165f86..a670d1eb 100644 --- a/lib/providers/strategy_provider.dart +++ b/lib/providers/strategy_provider.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; -import 'package:icarus/collab/convex_client.dart'; import 'package:flutter/foundation.dart' show kIsWeb; import 'package:icarus/const/transition_data.dart'; import 'package:icarus/const/placed_classes.dart'; @@ -441,12 +440,9 @@ class StrategyProvider extends Notifier { return null; } - return StrategyOp( + return StrategyPatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.strategy, - entityPublicId: strategyId, - expectedRevision: snapshot.header.revision, + expectedStrategyRevision: snapshot.header.revision, payload: { 'mapData': localMapData, if (localThemeProfileId != null) 'themeProfileId': localThemeProfileId, @@ -595,13 +591,11 @@ class StrategyProvider extends Notifier { final moved = ordered.removeAt(oldIndex); ordered.insert(targetIndex, moved); - final ack = await _enqueueCloudPageDescriptorOp(StrategyOp( + final ack = await _enqueueCloudPageDescriptorOp(PageReorderOp( opId: const Uuid().v4(), - kind: StrategyOpKind.reorder, - entityType: StrategyOpEntityType.page, - entityPublicId: moved.publicId, + pagePublicId: moved.publicId, sortIndex: targetIndex, - expectedRevision: snapshot.header.revision, + expectedStrategyRevision: snapshot.header.revision, )); if (ack != null) { await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); @@ -660,18 +654,16 @@ class StrategyProvider extends Notifier { ..sort((a, b) => a.sortIndex.compareTo(b.sortIndex)); final pageID = const Uuid().v4(); final nextIndex = pages.length; - final ack = await _enqueueCloudPageDescriptorOp(StrategyOp( + final ack = await _enqueueCloudPageDescriptorOp(PageAddOp( opId: const Uuid().v4(), - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.page, - entityPublicId: pageID, + pagePublicId: pageID, payload: { 'name': name ?? 'Page ${pages.length + 1}', 'isAttack': pages.isNotEmpty ? pages.last.isAttack : true, 'settings': ref.read(strategySettingsProvider).toJson(), }, sortIndex: nextIndex, - expectedRevision: snapshot.header.revision, + expectedStrategyRevision: snapshot.header.revision, )); if (ack?.isAck ?? false) { await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); @@ -736,13 +728,11 @@ class StrategyProvider extends Notifier { .where((candidate) => candidate.publicId == pageId) .firstOrNull; if (page == null) return; - final ack = await _enqueueCloudPageDescriptorOp(StrategyOp( + final ack = await _enqueueCloudPageDescriptorOp(PagePatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.page, - entityPublicId: pageId, + pagePublicId: pageId, payload: {'name': trimmed}, - expectedRevision: page.revision, + expectedPageRevision: page.revision, )); if (ack != null) { await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); @@ -789,12 +779,10 @@ class StrategyProvider extends Notifier { ); } - final ack = await _enqueueCloudPageDescriptorOp(StrategyOp( + final ack = await _enqueueCloudPageDescriptorOp(PageDeleteOp( opId: const Uuid().v4(), - kind: StrategyOpKind.delete, - entityType: StrategyOpEntityType.page, - entityPublicId: pageId, - expectedRevision: snapshot.header.revision, + pagePublicId: pageId, + expectedStrategyRevision: snapshot.header.revision, )); if (ack?.isAck ?? false) { await ref.read(remoteEditorSnapshotProvider.notifier).refresh(); @@ -1022,11 +1010,11 @@ class StrategyProvider extends Notifier { .read(convexStrategyRepositoryProvider) .fetchShell(strategyID); if (shell == null) return; - await ConvexClient.instance.mutation(name: "strategies:update", args: { - "strategyPublicId": strategyID, - "name": newName, - "expectedRevision": shell.header.revision, - }); + await ref.read(convexStrategyRepositoryProvider).updateStrategyName( + strategyPublicId: strategyID, + name: newName, + expectedRevision: shell.header.revision, + ); } catch (error, stackTrace) { final handled = await _reportCloudUnauthenticated( source: 'strategy:rename', @@ -1099,16 +1087,15 @@ class StrategyProvider extends Notifier { final page = fullPage.page; final newPageId = const Uuid().v4(); pageIdMap[page.publicId] = newPageId; - await ConvexClient.instance.mutation(name: "pages:add", args: { - "strategyPublicId": newStrategyID, - "pagePublicId": newPageId, - "name": page.name, - "sortIndex": page.sortIndex, - "isAttack": page.isAttack, - if (fullPage.content.settings != null) - "settings": fullPage.content.settings, - "expectedRevision": expectedStrategyRevision, - }); + await ref.read(convexStrategyRepositoryProvider).addPage( + strategyPublicId: newStrategyID, + pagePublicId: newPageId, + name: page.name, + sortIndex: page.sortIndex, + isAttack: page.isAttack, + settings: fullPage.content.settings, + expectedRevision: expectedStrategyRevision, + ); expectedStrategyRevision += 1; } @@ -1125,11 +1112,9 @@ class StrategyProvider extends Notifier { payloadMap.putIfAbsent("elementType", () => element.elementType); final newElementId = const Uuid().v4(); payloadMap["id"] = newElementId; - ops.add(StrategyOp( + ops.add(ElementAddOp( opId: const Uuid().v4(), - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.element, - entityPublicId: newElementId, + elementPublicId: newElementId, pagePublicId: newPageId, payload: cloudElementPayload( kind: @@ -1146,11 +1131,9 @@ class StrategyProvider extends Notifier { final newLineupId = const Uuid().v4(); final lineupPayload = cloudPayloadData(lineup.payload) ..["id"] = newLineupId; - ops.add(StrategyOp( + ops.add(LineupAddOp( opId: const Uuid().v4(), - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.lineup, - entityPublicId: newLineupId, + lineupPublicId: newLineupId, pagePublicId: newPageId, payload: cloudLineupGroupPayload(lineupPayload), sortIndex: lineup.sortIndex, @@ -1215,10 +1198,10 @@ class StrategyProvider extends Notifier { final shell = await ref .read(convexStrategyRepositoryProvider) .fetchShell(strategyID); - await ConvexClient.instance.mutation(name: "strategies:delete", args: { - "strategyPublicId": strategyID, - "expectedRevision": shell.header.revision, - }); + await ref.read(convexStrategyRepositoryProvider).deleteStrategy( + strategyPublicId: strategyID, + expectedRevision: shell.header.revision, + ); } catch (error, stackTrace) { final handled = await _reportCloudUnauthenticated( source: 'strategy:delete', @@ -1369,18 +1352,13 @@ class StrategyProvider extends Notifier { final ops = [ for (final fullPage in snapshot.pages) - StrategyOp( + PageContentPatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.pageContent, - entityPublicId: fullPage.page.publicId, pagePublicId: fullPage.page.publicId, - payload: { - 'settings': transform(_settingsFromPayloadOrDefault( - fullPage.content.settings, - )).toJson(), - }, - expectedRevision: fullPage.content.revision, + settings: transform(_settingsFromPayloadOrDefault( + fullPage.content.settings, + )).toJson(), + expectedPageContentRevision: fullPage.content.revision, ), ]; @@ -1453,11 +1431,11 @@ class StrategyProvider extends Notifier { final shell = await ref .read(convexStrategyRepositoryProvider) .fetchShell(strategyID); - await ConvexClient.instance.mutation(name: "strategies:move", args: { - "strategyPublicId": strategyID, - if (parentID != null) "folderPublicId": parentID, - "expectedRevision": shell.header.revision, - }); + await ref.read(convexStrategyRepositoryProvider).moveStrategy( + strategyPublicId: strategyID, + folderPublicId: parentID, + expectedRevision: shell.header.revision, + ); } catch (error, stackTrace) { await _reportCloudUnauthenticated( source: 'strategy:move', diff --git a/lib/strategy/strategy_cloud_migration.dart b/lib/strategy/strategy_cloud_migration.dart index 5745faf5..e449f4aa 100644 --- a/lib/strategy/strategy_cloud_migration.dart +++ b/lib/strategy/strategy_cloud_migration.dart @@ -77,11 +77,9 @@ void appendMigratedPageOps( final lineupId = nextUniqueMigrationId(group.id, usedLineupIds); final lineupPayload = cloudLineupPayload(group)..['id'] = lineupId; ops.add( - StrategyOp( + LineupAddOp( opId: const Uuid().v4(), - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.lineup, - entityPublicId: lineupId, + lineupPublicId: lineupId, pagePublicId: page.id, payload: cloudLineupGroupPayload(lineupPayload), sortIndex: lineupOrder++, @@ -108,11 +106,9 @@ StrategyOp buildMigratedElementOp( Map payload, int sortIndex, ) { - return StrategyOp( + return ElementAddOp( opId: const Uuid().v4(), - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.element, - entityPublicId: elementId, + elementPublicId: elementId, pagePublicId: pagePublicId, payload: cloudElementPayload( kind: payload['elementType'] as String? ?? 'generic', diff --git a/lib/strategy/strategy_import_export.dart b/lib/strategy/strategy_import_export.dart index 8eaef93e..9bdec4cb 100644 --- a/lib/strategy/strategy_import_export.dart +++ b/lib/strategy/strategy_import_export.dart @@ -2401,8 +2401,13 @@ class StrategyImportExportService { assetIds.add(image.id); } } - } catch (_) { - continue; + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud lineup ${lineup.publicId} could not be exported: $error', + ), + stackTrace, + ); } } } @@ -2452,8 +2457,15 @@ class StrategyImportExportService { await zipStrategy(id: id, outputFilePath: outputFile); } - StrategyData _strategyDataFromRemoteSnapshot( - RemoteFullStrategySnapshot snapshot) { + @visibleForTesting + static StrategyData strategyDataFromRemoteSnapshotForTest( + RemoteFullStrategySnapshot snapshot, + ) => + _strategyDataFromRemoteSnapshot(snapshot); + + static StrategyData _strategyDataFromRemoteSnapshot( + RemoteFullStrategySnapshot snapshot, + ) { final pages = []; final mapValue = Maps.mapNames.entries .where((entry) => entry.value == snapshot.header.mapData) @@ -2497,8 +2509,19 @@ class StrategyImportExportService { case 'utility': utilityData.add(PlacedUtility.fromJson(payload)); break; + default: + throw FormatException( + 'Unsupported cloud element type: ${element.elementType}', + ); } - } catch (_) {} + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud element ${element.publicId} could not be exported: $error', + ), + stackTrace, + ); + } } final parsedLineUpGroups = []; @@ -2508,7 +2531,14 @@ class StrategyImportExportService { parsedLineUpGroups.add( LineUpGroup.fromJson(cloudPayloadData(lineup.payload)), ); - } catch (_) {} + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud lineup ${lineup.publicId} could not be exported: $error', + ), + stackTrace, + ); + } } StrategySettings settings = StrategySettings(); @@ -2516,7 +2546,15 @@ class StrategyImportExportService { if (settingsPayload != null && settingsPayload.isNotEmpty) { try { settings = StrategySettings.fromJson(settingsPayload); - } catch (_) {} + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud page ${remotePage.publicId} settings could not be ' + 'exported: $error', + ), + stackTrace, + ); + } } pages.add( @@ -2542,7 +2580,15 @@ class StrategyImportExportService { if (rawPalette != null && rawPalette.isNotEmpty) { try { overridePalette = MapThemePalette.fromJson(rawPalette); - } catch (_) {} + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud strategy ${snapshot.header.publicId} theme could not be ' + 'exported: $error', + ), + stackTrace, + ); + } } return StrategyData( diff --git a/lib/strategy/strategy_page_source.dart b/lib/strategy/strategy_page_source.dart index 796b49c7..9496ac0b 100644 --- a/lib/strategy/strategy_page_source.dart +++ b/lib/strategy/strategy_page_source.dart @@ -266,8 +266,13 @@ class CloudStrategyPageSource implements StrategyPageSource { parsedLineUpGroups.add(LineUpGroup.fromJson( cloudPayloadData(lineup.payload), )); - } catch (_) { - // Ignore malformed payloads during hydration. + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud lineup ${lineup.publicId} could not be hydrated: $error', + ), + stackTrace, + ); } } @@ -370,12 +375,10 @@ class CloudStrategyPageSource implements StrategyPageSource { await ref.read(strategyOpQueueProvider.notifier).syncDesiredGenericOp( entityKey: const EntitySyncKey.strategy(), - desiredOp: StrategyOp( + desiredOp: StrategyPatchOp( opId: const Uuid().v4(), - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.strategy, payload: payload, - expectedRevision: header.revision, + expectedStrategyRevision: header.revision, ), flushImmediately: false, ); @@ -439,8 +442,13 @@ class CloudStrategyPageSource implements StrategyPageSource { parsedLineUpGroups.add(LineUpGroup.fromJson( cloudPayloadData(lineup.payload), )); - } catch (_) { - // Ignore malformed payloads during hydration. + } catch (error, stackTrace) { + Error.throwWithStackTrace( + FormatException( + 'Cloud lineup ${lineup.publicId} could not be hydrated: $error', + ), + stackTrace, + ); } } diff --git a/lib/widgets/account_avatar.dart b/lib/widgets/account_avatar.dart new file mode 100644 index 00000000..4d6f0cbd --- /dev/null +++ b/lib/widgets/account_avatar.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class AccountAvatar extends StatelessWidget { + const AccountAvatar({ + super.key, + required this.radius, + required this.backgroundColor, + required this.fallback, + this.avatarUrl, + }); + + final double radius; + final Color backgroundColor; + final Widget fallback; + final String? avatarUrl; + + @override + Widget build(BuildContext context) { + final image = avatarUrl == null ? null : NetworkImage(avatarUrl!); + return CircleAvatar( + radius: radius, + backgroundColor: backgroundColor, + foregroundImage: image, + onForegroundImageError: image == null ? null : (_, __) {}, + child: fallback, + ); + } +} diff --git a/lib/widgets/current_path_bar.dart b/lib/widgets/current_path_bar.dart index ab311717..2b73f039 100644 --- a/lib/widgets/current_path_bar.dart +++ b/lib/widgets/current_path_bar.dart @@ -20,7 +20,7 @@ class CurrentPathBar extends ConsumerWidget { final currentFolderId = ref.watch(folderProvider); final cloudFolders = isCloud ? (ref.watch(cloudAllFoldersProvider).valueOrNull ?? const []) - .map(FolderProvider.cloudSummaryToFolder) + .map((entry) => entry.folder) .toList(growable: false) : null; final currentFolder = currentFolderId == null @@ -134,9 +134,7 @@ class FolderTab extends ConsumerWidget { final targetSection = folder == null ? CloudLibrarySection.home : ref.read(cloudLibrarySectionProvider); - ref - .read(cloudLibrarySectionProvider.notifier) - .select(targetSection); + ref.read(cloudLibrarySectionProvider.notifier).select(targetSection); } ref.read(folderProvider.notifier).updateID(folder?.id); }, diff --git a/lib/widgets/custom_text_field.dart b/lib/widgets/custom_text_field.dart index fea2a0e5..669b8f25 100644 --- a/lib/widgets/custom_text_field.dart +++ b/lib/widgets/custom_text_field.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:icarus/widgets/text_editing_shortcut_scope.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; -class CustomTextField extends ConsumerWidget { +class CustomTextField extends StatefulWidget { const CustomTextField({ super.key, this.controller, + this.focusNode, this.hintText, this.textAlign, this.minLines, @@ -19,6 +19,7 @@ class CustomTextField extends ConsumerWidget { this.hasError = false, }); final TextEditingController? controller; + final FocusNode? focusNode; final String? hintText; final TextAlign? textAlign; final int? minLines; @@ -33,27 +34,141 @@ class CustomTextField extends ConsumerWidget { final bool hasError; @override - Widget build(BuildContext context, WidgetRef ref) { - return TextEditingShortcutScope( - child: ShadInput( - decoration: hasError - ? ShadDecoration( - border: ShadBorder.all( - color: ShadTheme.of(context).colorScheme.destructive, - ), - ) - : null, - controller: controller, - textAlign: textAlign ?? TextAlign.start, - minLines: minLines, - maxLines: maxLines ?? 1, - keyboardType: keyboardType, - autofillHints: autofillHints, - obscureText: obscureText, - textInputAction: textInputAction, - placeholder: hintText != null ? Text(hintText!) : null, - onSubmitted: onSubmitted, + State createState() => _CustomTextFieldState(); +} + +class _CustomTextFieldState extends State { + late final TextEditingController _fallbackController; + late final FocusNode _fallbackFocusNode; + + TextEditingController get _controller => + widget.controller ?? _fallbackController; + FocusNode get _focusNode => widget.focusNode ?? _fallbackFocusNode; + + @override + void initState() { + super.initState(); + _fallbackController = TextEditingController(); + _fallbackFocusNode = FocusNode(); + } + + @override + void dispose() { + _fallbackController.dispose(); + _fallbackFocusNode.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return _EditableTextFieldSemantics( + label: widget.hintText, + controller: _controller, + focusNode: _focusNode, + obscureText: widget.obscureText, + child: TextEditingShortcutScope( + child: ShadInput( + decoration: widget.hasError + ? ShadDecoration( + border: ShadBorder.all( + color: ShadTheme.of(context).colorScheme.destructive, + ), + ) + : null, + controller: _controller, + focusNode: _focusNode, + textAlign: widget.textAlign ?? TextAlign.start, + minLines: widget.minLines, + maxLines: widget.maxLines ?? 1, + keyboardType: widget.keyboardType, + autofillHints: widget.autofillHints, + obscureText: widget.obscureText, + textInputAction: widget.textInputAction, + placeholder: widget.hintText != null ? Text(widget.hintText!) : null, + onSubmitted: widget.onSubmitted, + ), ), ); } } + +class _EditableTextFieldSemantics extends StatefulWidget { + const _EditableTextFieldSemantics({ + required this.label, + required this.controller, + required this.focusNode, + required this.obscureText, + required this.child, + }); + + final String? label; + final TextEditingController controller; + final FocusNode focusNode; + final bool obscureText; + final Widget child; + + @override + State<_EditableTextFieldSemantics> createState() => + _EditableTextFieldSemanticsState(); +} + +class _EditableTextFieldSemanticsState + extends State<_EditableTextFieldSemantics> { + @override + void initState() { + super.initState(); + widget.controller.addListener(_refresh); + widget.focusNode.addListener(_refresh); + } + + @override + void didUpdateWidget(covariant _EditableTextFieldSemantics oldWidget) { + super.didUpdateWidget(oldWidget); + if (!identical(oldWidget.controller, widget.controller)) { + oldWidget.controller.removeListener(_refresh); + widget.controller.addListener(_refresh); + } + if (!identical(oldWidget.focusNode, widget.focusNode)) { + oldWidget.focusNode.removeListener(_refresh); + widget.focusNode.addListener(_refresh); + } + } + + @override + void dispose() { + widget.controller.removeListener(_refresh); + widget.focusNode.removeListener(_refresh); + super.dispose(); + } + + void _refresh() { + if (mounted) setState(() {}); + } + + void _setText(String value) { + widget.controller.value = TextEditingValue( + text: value, + selection: TextSelection.collapsed(offset: value.length), + ); + widget.focusNode.requestFocus(); + } + + @override + Widget build(BuildContext context) { + final text = widget.controller.text; + return Semantics( + label: widget.label, + value: widget.obscureText ? '•' * text.length : text, + textField: true, + enabled: true, + obscured: widget.obscureText, + focusable: true, + focused: widget.focusNode.hasFocus, + onTap: widget.focusNode.requestFocus, + onSetText: _setText, + onDidGainAccessibilityFocus: widget.focusNode.requestFocus, + excludeSemantics: true, + child: widget.child, + ); + } +} diff --git a/lib/widgets/dialogs/auth/auth_dialog.dart b/lib/widgets/dialogs/auth/auth_dialog.dart index f4a9182d..9a5df12b 100644 --- a/lib/widgets/dialogs/auth/auth_dialog.dart +++ b/lib/widgets/dialogs/auth/auth_dialog.dart @@ -28,6 +28,9 @@ class _AuthDialogState extends ConsumerState { final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _confirmController = TextEditingController(); + final FocusNode _emailFocusNode = FocusNode(); + final FocusNode _passwordFocusNode = FocusNode(); + final FocusNode _confirmFocusNode = FocusNode(); bool _submitting = false; bool _isSignUp = false; bool _waitingForDiscord = false; @@ -46,6 +49,9 @@ class _AuthDialogState extends ConsumerState { _emailController.dispose(); _passwordController.dispose(); _confirmController.dispose(); + _emailFocusNode.dispose(); + _passwordFocusNode.dispose(); + _confirmFocusNode.dispose(); super.dispose(); } @@ -158,53 +164,41 @@ class _AuthDialogState extends ConsumerState { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - MergeSemantics( - child: Semantics( - key: const ValueKey('auth-email-field'), - label: 'Email', - child: CustomTextField( - controller: _emailController, - keyboardType: TextInputType.emailAddress, - autofillHints: const [AutofillHints.email], - hintText: 'Email', - textInputAction: TextInputAction.next, - hasError: _errorField == _AuthField.email, - ), - ), + CustomTextField( + key: const ValueKey('auth-email-field'), + controller: _emailController, + focusNode: _emailFocusNode, + keyboardType: TextInputType.emailAddress, + autofillHints: const [AutofillHints.email], + hintText: 'Email', + textInputAction: TextInputAction.next, + hasError: _errorField == _AuthField.email, ), const SizedBox(height: 10), - MergeSemantics( - child: Semantics( - key: const ValueKey('auth-password-field'), - label: 'Password', - child: CustomTextField( - controller: _passwordController, - obscureText: true, - autofillHints: const [AutofillHints.password], - hintText: 'Password', - textInputAction: - _isSignUp ? TextInputAction.next : TextInputAction.done, - onSubmitted: _isSignUp ? null : (_) => _submit(), - hasError: _errorField == _AuthField.password, - ), - ), + CustomTextField( + key: const ValueKey('auth-password-field'), + controller: _passwordController, + focusNode: _passwordFocusNode, + obscureText: true, + autofillHints: const [AutofillHints.password], + hintText: 'Password', + textInputAction: + _isSignUp ? TextInputAction.next : TextInputAction.done, + onSubmitted: _isSignUp ? null : (_) => _submit(), + hasError: _errorField == _AuthField.password, ), if (_isSignUp) ...[ const SizedBox(height: 10), - MergeSemantics( - child: Semantics( - key: const ValueKey('auth-confirm-password-field'), - label: 'Confirm password', - child: CustomTextField( - controller: _confirmController, - obscureText: true, - autofillHints: const [AutofillHints.password], - hintText: 'Confirm password', - textInputAction: TextInputAction.done, - onSubmitted: (_) => _submit(), - hasError: _errorField == _AuthField.confirm, - ), - ), + CustomTextField( + key: const ValueKey('auth-confirm-password-field'), + controller: _confirmController, + focusNode: _confirmFocusNode, + obscureText: true, + autofillHints: const [AutofillHints.password], + hintText: 'Confirm password', + textInputAction: TextInputAction.done, + onSubmitted: (_) => _submit(), + hasError: _errorField == _AuthField.confirm, ), ], const SizedBox(height: 12), diff --git a/lib/widgets/dialogs/confirm_alert_dialog.dart b/lib/widgets/dialogs/confirm_alert_dialog.dart index 20326449..202f4f76 100644 --- a/lib/widgets/dialogs/confirm_alert_dialog.dart +++ b/lib/widgets/dialogs/confirm_alert_dialog.dart @@ -20,6 +20,9 @@ class ConfirmAlertDialog extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + void cancel() => Navigator.of(context).pop(false); + void confirm() => Navigator.of(context).pop(true); + return ShadDialog.alert( title: Text(title), description: Padding( @@ -27,25 +30,40 @@ class ConfirmAlertDialog extends ConsumerWidget { child: Text(content), ), actions: [ - ShadButton.secondary( - child: Text(cancelText), - onPressed: () { - Navigator.of(context).pop(false); - }, + Semantics( + key: const ValueKey('confirm-alert-cancel'), + label: cancelText, + button: true, + onTap: cancel, + excludeSemantics: true, + child: ShadButton.secondary( + onPressed: cancel, + child: Text(cancelText), + ), ), if (isDestructive) - ShadButton.destructive( - child: Text(confirmText), - onPressed: () { - Navigator.of(context).pop(true); - }, + Semantics( + key: const ValueKey('confirm-alert-confirm'), + label: confirmText, + button: true, + onTap: confirm, + excludeSemantics: true, + child: ShadButton.destructive( + onPressed: confirm, + child: Text(confirmText), + ), ) else - ShadButton( - child: Text(confirmText), - onPressed: () { - Navigator.of(context).pop(true); - }, + Semantics( + key: const ValueKey('confirm-alert-confirm'), + label: confirmText, + button: true, + onTap: confirm, + excludeSemantics: true, + child: ShadButton( + onPressed: confirm, + child: Text(confirmText), + ), ), ], ); diff --git a/lib/widgets/draggable_widgets/placed_widget_builder.dart b/lib/widgets/draggable_widgets/placed_widget_builder.dart index 1f698522..aa47b07e 100644 --- a/lib/widgets/draggable_widgets/placed_widget_builder.dart +++ b/lib/widgets/draggable_widgets/placed_widget_builder.dart @@ -926,7 +926,20 @@ class _LineUpAgents extends ConsumerWidget { return Stack( clipBehavior: Clip.none, children: [ - for (final group in groups) LineUpGroupAgentWidget(group: group), + for (final group in groups) + LineUpGroupAgentWidget( + group: group, + onDragEnd: (details) { + final renderBox = context.findRenderObject() as RenderBox; + final localOffset = renderBox.globalToLocal(details.offset); + final position = + CoordinateSystem.instance.screenToCoordinate(localOffset); + ref.read(lineUpProvider.notifier).updateGroupAgentPosition( + groupId: group.id, + position: position, + ); + }, + ), ], ); } @@ -945,7 +958,21 @@ class _LineUpAbilities extends ConsumerWidget { children: [ for (final group in groups) for (final item in group.items) - LineUpItemAbilityWidget(groupId: group.id, item: item), + LineUpItemAbilityWidget( + groupId: group.id, + item: item, + onDragEnd: (details) { + final renderBox = context.findRenderObject() as RenderBox; + final localOffset = renderBox.globalToLocal(details.offset); + final position = + CoordinateSystem.instance.screenToCoordinate(localOffset); + ref.read(lineUpProvider.notifier).updateItemAbilityPosition( + groupId: group.id, + itemId: item.id, + position: position, + ); + }, + ), ], ); } diff --git a/lib/widgets/folder_content.dart b/lib/widgets/folder_content.dart index d1d46fc2..7d5e7bb5 100644 --- a/lib/widgets/folder_content.dart +++ b/lib/widgets/folder_content.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_ce_flutter/adapters.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/const/hive_boxes.dart'; import 'package:icarus/const/settings.dart'; import 'package:icarus/providers/collab/remote_library_provider.dart'; @@ -121,7 +121,7 @@ class FolderContent extends ConsumerWidget { ); } final folders = (foldersAsync.valueOrNull ?? const []) - .map(FolderProvider.cloudSummaryToFolder) + .map((entry) => entry.folder) .toList(growable: false); final strategies = strategiesAsync.valueOrNull ?? const []; final isSharedWithMe = cloudSection == CloudLibrarySection.sharedWithMe; @@ -200,24 +200,27 @@ class FolderContent extends ConsumerWidget { return filtered; } - List _filterCloudStrategies( + List _filterCloudStrategies( WidgetRef ref, - List strategies, + List strategies, ) { final search = ref.watch(strategySearchQueryProvider).trim().toLowerCase(); final filter = ref.watch(strategyFilterProvider); final filtered = [...strategies]; if (search.isNotEmpty) { filtered.retainWhere( - (strategy) => strategy.name.toLowerCase().contains(search), + (entry) => entry.strategy.name.toLowerCase().contains(search), ); } - Comparator comparator = switch (filter.sortBy) { - SortBy.alphabetical => (a, b) => - a.name.toLowerCase().compareTo(b.name.toLowerCase()), - SortBy.dateCreated => (a, b) => a.createdAt.compareTo(b.createdAt), - SortBy.dateUpdated => (a, b) => a.updatedAt.compareTo(b.updatedAt), + Comparator comparator = switch (filter.sortBy) { + SortBy.alphabetical => (a, b) => a.strategy.name + .toLowerCase() + .compareTo(b.strategy.name.toLowerCase()), + SortBy.dateCreated => (a, b) => + a.strategy.createdAt.compareTo(b.strategy.createdAt), + SortBy.dateUpdated => (a, b) => + a.strategy.lastEdited.compareTo(b.strategy.lastEdited), }; final direction = filter.sortOrder == SortOrder.ascending ? 1 : -1; @@ -230,7 +233,7 @@ class FolderContent extends ConsumerWidget { WidgetRef ref, { required List folders, required List localStrategies, - required List cloudStrategies, + required List cloudStrategies, required bool isCloud, Key? emptyStateKey, IconData? emptyStateIcon, diff --git a/lib/widgets/folder_navigator.dart b/lib/widgets/folder_navigator.dart index c0ea58e6..63d6c977 100644 --- a/lib/widgets/folder_navigator.dart +++ b/lib/widgets/folder_navigator.dart @@ -30,6 +30,7 @@ import 'package:icarus/widgets/dialogs/confirm_alert_dialog.dart'; import 'package:icarus/widgets/dialogs/share_links_dialog.dart'; import 'package:icarus/widgets/dialogs/strategy/create_strategy_dialog.dart'; import 'package:icarus/widgets/dialogs/web_view_dialog.dart'; +import 'package:icarus/widgets/account_avatar.dart'; import 'package:icarus/widgets/folder_content.dart'; import 'package:icarus/widgets/folder_edit_dialog.dart'; import 'package:icarus/widgets/ica_drop_target.dart'; @@ -1025,17 +1026,11 @@ class _AccountAvatar extends StatelessWidget { @override Widget build(BuildContext context) { - if (isAuthenticated && avatarUrl != null) { - return CircleAvatar( - radius: 14, - backgroundImage: NetworkImage(avatarUrl!), - ); - } - - return CircleAvatar( + return AccountAvatar( radius: 14, backgroundColor: Settings.tacticalVioletTheme.card, - child: Icon( + avatarUrl: isAuthenticated ? avatarUrl : null, + fallback: Icon( isAuthenticated ? Icons.person : LucideIcons.userRound, size: 15, ), diff --git a/lib/widgets/folder_navigator_sidebar.dart b/lib/widgets/folder_navigator_sidebar.dart index 85eca5ee..e72c2cbc 100644 --- a/lib/widgets/folder_navigator_sidebar.dart +++ b/lib/widgets/folder_navigator_sidebar.dart @@ -55,7 +55,7 @@ class FolderNavigatorSidebar extends ConsumerWidget { if (isCloud) { final cloudFolders = (ref.watch(cloudAllFoldersProvider).valueOrNull ?? const []) - .map(FolderProvider.cloudSummaryToFolder) + .map((entry) => entry.folder) .toList(growable: false); return _SidebarShell( folders: cloudFolders, @@ -530,8 +530,7 @@ class _FolderSidebarItemState extends ConsumerState<_FolderSidebarItem> { final selected = widget.selectedFolderId == folder.id; final hasChildren = widget.node.children.isNotEmpty; final showChildren = hasChildren && (_expanded || widget.forceExpanded); - final showMenuButton = - _hovered || selected || _menuButtonController.isOpen; + final showMenuButton = _hovered || selected || _menuButtonController.isOpen; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -661,8 +660,8 @@ class _FolderSidebarItemState extends ConsumerState<_FolderSidebarItem> { final allFolders = ref.read(cloudAllFoldersProvider).valueOrNull ?? const []; final cloudRole = allFolders - .where((item) => item.publicId == folder.id) - .map((item) => item.role) + .where((entry) => entry.folder.id == folder.id) + .map((entry) => entry.role) .firstOrNull; final canManage = !isCloud || cloudRole == 'owner'; diff --git a/lib/widgets/folder_pill.dart b/lib/widgets/folder_pill.dart index 168103a0..ce5c00b3 100644 --- a/lib/widgets/folder_pill.dart +++ b/lib/widgets/folder_pill.dart @@ -74,8 +74,8 @@ class _FolderPillState extends ConsumerState final allFolders = ref.read(cloudAllFoldersProvider).valueOrNull ?? const []; return allFolders - .where((folder) => folder.publicId == widget.folder.id) - .map((folder) => folder.role) + .where((entry) => entry.folder.id == widget.folder.id) + .map((entry) => entry.role) .firstOrNull; } diff --git a/lib/widgets/line_up_widget.dart b/lib/widgets/line_up_widget.dart index aa073f66..6a978da6 100644 --- a/lib/widgets/line_up_widget.dart +++ b/lib/widgets/line_up_widget.dart @@ -4,34 +4,62 @@ import 'package:icarus/const/agents.dart'; import 'package:icarus/const/coordinate_system.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'; import 'package:icarus/providers/map_provider.dart'; +import 'package:icarus/providers/screen_zoom_provider.dart'; import 'package:icarus/providers/strategy_settings_provider.dart'; import 'package:icarus/widgets/draggable_widgets/ability/ability_visibility_context_menu.dart'; import 'package:icarus/widgets/draggable_widgets/agents/agent_widget.dart'; +import 'package:icarus/widgets/draggable_widgets/zoom_transform.dart'; class LineUpGroupAgentWidget extends ConsumerWidget { LineUpGroupAgentWidget({ Key? key, required this.group, + this.onDragEnd, }) : super(key: key ?? ValueKey('lineup-agent-widget-${group.id}')); final LineUpGroup group; + final ValueChanged? onDragEnd; @override Widget build(BuildContext context, WidgetRef ref) { final coordinateSystem = CoordinateSystem.instance; - final agentScreen = coordinateSystem.coordinateToScreen(group.agent.position); + final agentScreen = + coordinateSystem.coordinateToScreen(group.agent.position); + final child = AgentWidget( + lineUpId: group.id, + agent: AgentData.agents[group.agent.type]!, + isAlly: group.agent.isAlly, + id: group.agent.id, + ); return Positioned( key: ValueKey('lineup-agent-${group.id}'), left: agentScreen.dx, top: agentScreen.dy, - child: AgentWidget( - lineUpId: group.id, - agent: AgentData.agents[group.agent.type]!, - isAlly: group.agent.isAlly, - id: group.agent.id, - ), + child: onDragEnd == null + ? child + : Draggable( + key: ValueKey('lineup-agent-drag-${group.id}'), + data: group.agent, + dragAnchorStrategy: + ref.read(screenZoomProvider.notifier).zoomDragAnchorStrategy, + feedback: Opacity( + opacity: Settings.feedbackOpacity, + child: ZoomTransform( + child: AgentWidget( + agent: AgentData.agents[group.agent.type]!, + isAlly: group.agent.isAlly, + id: '', + ), + ), + ), + childWhenDragging: const SizedBox.shrink(), + onDragEnd: onDragEnd, + child: child, + ), ); } } @@ -41,12 +69,14 @@ class LineUpItemAbilityWidget extends ConsumerWidget { Key? key, required this.groupId, required this.item, + this.onDragEnd, }) : super( key: key ?? ValueKey('lineup-ability-widget-$groupId-${item.id}'), ); final String groupId; final LineUpItem item; + final ValueChanged? onDragEnd; @override Widget build(BuildContext context, WidgetRef ref) { @@ -65,48 +95,59 @@ class LineUpItemAbilityWidget extends ConsumerWidget { lineUpItemId: item.id, includeDelete: true, ); - final rawAbilityChild = isRotatable - ? Transform.rotate( - angle: item.ability.rotation, - alignment: Alignment.topLeft, - origin: item.ability.data.abilityData! - .getAnchorPoint(mapScale: mapScale, abilitySize: abilitySize) - .scale( - coordinateSystem.scaleFactor, - coordinateSystem.scaleFactor, - ), - child: item.ability.data.abilityData!.createWidget( - id: null, - isAlly: item.ability.isAlly, - mapScale: mapScale, - lineUpId: groupId, - lineUpItemId: item.id, - rotation: item.ability.rotation, - length: item.ability.length, - armLengthsMeters: item.ability.armLengthsMeters, - visualState: item.ability.visualState, - watchMouse: true, - contextMenuItems: contextMenuItems, + Widget buildAbilityChild({required bool watchMouse}) { + final child = item.ability.data.abilityData!.createWidget( + id: null, + isAlly: item.ability.isAlly, + mapScale: mapScale, + lineUpId: groupId, + lineUpItemId: item.id, + rotation: item.ability.rotation, + length: item.ability.length, + armLengthsMeters: item.ability.armLengthsMeters, + visualState: item.ability.visualState, + watchMouse: watchMouse, + contextMenuItems: contextMenuItems, + ); + if (!isRotatable) return child; + return Transform.rotate( + angle: item.ability.rotation, + alignment: Alignment.topLeft, + origin: item.ability.data.abilityData! + .getAnchorPoint(mapScale: mapScale, abilitySize: abilitySize) + .scale( + coordinateSystem.scaleFactor, + coordinateSystem.scaleFactor, ), - ) - : item.ability.data.abilityData!.createWidget( - id: null, - isAlly: item.ability.isAlly, - mapScale: mapScale, - lineUpId: groupId, - lineUpItemId: item.id, - rotation: item.ability.rotation, - length: item.ability.length, - armLengthsMeters: item.ability.armLengthsMeters, - visualState: item.ability.visualState, - watchMouse: true, - contextMenuItems: contextMenuItems, - ); + child: child, + ); + } + + final rawAbilityChild = buildAbilityChild(watchMouse: true); return Positioned( key: ValueKey('lineup-ability-${item.id}'), left: abilityScreen.dx, top: abilityScreen.dy, - child: rawAbilityChild, + child: onDragEnd == null + ? rawAbilityChild + : Draggable( + key: ValueKey('lineup-ability-drag-${item.id}'), + data: item.ability, + dragAnchorStrategy: + ref.read(screenZoomProvider.notifier).zoomDragAnchorStrategy, + feedback: Opacity( + opacity: Settings.feedbackOpacity, + child: ZoomTransform( + child: buildAbilityChild(watchMouse: false), + ), + ), + childWhenDragging: const SizedBox.shrink(), + onDragEnd: onDragEnd, + child: ColoredBox( + color: Colors.transparent, + child: rawAbilityChild, + ), + ), ); } } diff --git a/lib/widgets/save_and_load_button.dart b/lib/widgets/save_and_load_button.dart index 9e7b0d14..c98d6999 100644 --- a/lib/widgets/save_and_load_button.dart +++ b/lib/widgets/save_and_load_button.dart @@ -65,14 +65,25 @@ class _SaveAndLoadButtonState extends ConsumerState { if (kIsWeb) { Settings.showToast( message: - 'This feature is only supported in the Windows version.', + 'This feature is only supported in the desktop app.', backgroundColor: Settings.tacticalVioletTheme.destructive, ); return; } - await StrategyImportExportService(ref) - .exportFile(ref.read(strategyProvider).strategyId!); + final strategy = ref.read(strategyProvider); + final strategyId = strategy.strategyId; + if (strategyId == null || strategy.source == null) { + throw StateError('No strategy is open for export.'); + } + + final exporter = StrategyImportExportService(ref); + switch (strategy.source!) { + case StrategySource.cloud: + await exporter.exportCloudStrategy(strategyId); + case StrategySource.local: + await exporter.exportFile(strategyId); + } }, icon: const Icon(Icons.file_upload), ), @@ -85,7 +96,7 @@ class _SaveAndLoadButtonState extends ConsumerState { if (kIsWeb) { Settings.showToast( message: - 'This feature is only supported in the Windows version.', + 'This feature is only supported in the desktop app.', backgroundColor: Settings.tacticalVioletTheme.destructive, ); return; diff --git a/lib/widgets/settings_tab.dart b/lib/widgets/settings_tab.dart index a259e5c4..a03fa841 100644 --- a/lib/widgets/settings_tab.dart +++ b/lib/widgets/settings_tab.dart @@ -7,6 +7,7 @@ import 'package:icarus/const/settings.dart'; import 'package:icarus/const/shortcut_info.dart'; import 'package:icarus/providers/auth_provider.dart'; import 'package:icarus/widgets/dialogs/auth/auth_dialog.dart'; +import 'package:icarus/widgets/account_avatar.dart'; import 'package:icarus/providers/map_provider.dart'; import 'package:icarus/providers/user_preferences_provider.dart'; import 'package:icarus/providers/marker_sizes_sync.dart'; @@ -1181,12 +1182,11 @@ class _SignedInAccountRow extends ConsumerWidget { padding: const EdgeInsets.symmetric(vertical: 10), child: Row( children: [ - CircleAvatar( + AccountAvatar( radius: 16, backgroundColor: theme.muted, - foregroundImage: - avatarUrl != null ? NetworkImage(avatarUrl) : null, - child: Text( + avatarUrl: avatarUrl, + fallback: Text( authState.displayName.characters.first.toUpperCase(), style: TextStyle( fontSize: 13, diff --git a/lib/widgets/strategy_tile/strategy_tile.dart b/lib/widgets/strategy_tile/strategy_tile.dart index 10980991..7e05a6ce 100644 --- a/lib/widgets/strategy_tile/strategy_tile.dart +++ b/lib/widgets/strategy_tile/strategy_tile.dart @@ -1,7 +1,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/const/maps.dart'; import 'package:icarus/const/settings.dart'; import 'package:icarus/providers/strategy_provider.dart'; @@ -36,7 +36,7 @@ class StrategyTile extends ConsumerStatefulWidget { }) : strategyData = null; final StrategyData? strategyData; - final CloudStrategySummary? cloudStrategy; + final CloudStrategyEntry? cloudStrategy; final bool canRename; final bool canDuplicate; final bool canDelete; @@ -58,19 +58,14 @@ class _StrategyTileState extends ConsumerState { bool get _isCloud => widget.cloudStrategy != null; bool get _canShare => _isCloud && widget.cloudStrategy?.role == 'owner'; String get _strategyId => - widget.strategyData?.id ?? widget.cloudStrategy!.publicId; + widget.strategyData?.id ?? widget.cloudStrategy!.strategy.id; String get _strategyName => - widget.strategyData?.name ?? widget.cloudStrategy!.name; + widget.strategyData?.name ?? widget.cloudStrategy!.strategy.name; MapValue? get _mapValue { final strategy = widget.strategyData; if (strategy != null) return strategy.mapData; - final mapData = widget.cloudStrategy?.mapData; - if (mapData == null) return null; - for (final entry in Maps.mapNames.entries) { - if (entry.value == mapData) return entry.key; - } - return null; + return widget.cloudStrategy?.strategy.mapData; } bool get _isAttack { @@ -83,7 +78,7 @@ class _StrategyTileState extends ConsumerState { StrategyTileViewData get _viewData => widget.strategyData != null ? StrategyTileViewData.fromStrategy(widget.strategyData!) - : StrategyTileViewData.fromCloudSummary(widget.cloudStrategy!); + : StrategyTileViewData.fromCloudEntry(widget.cloudStrategy!); @override void dispose() { @@ -151,13 +146,9 @@ class _StrategyTileState extends ConsumerState { child: ShadContextMenuRegion( controller: _menuButtonController, items: _buildMenuItems(), - child: ShadIconButton.secondary( - width: 28, - height: 28, - onPressed: () { - _menuButtonController.toggle(); - }, - icon: const Icon(Icons.more_vert_outlined), + child: StrategyTileActionsButton( + strategyName: _strategyName, + onPressed: _menuButtonController.toggle, ), ), ), @@ -171,43 +162,70 @@ class _StrategyTileState extends ConsumerState { ); } - List _buildMenuItems() { + List _buildMenuItems() { return [ - ShadContextMenuItem( + _buildMenuItem( + label: 'Rename', leading: const Icon(LucideIcons.pencil), - onPressed: widget.canRename ? () => _showRenameDialog() : null, - child: const Text('Rename'), + enabled: widget.canRename, + onPressed: _showRenameDialog, ), - ShadContextMenuItem( + _buildMenuItem( + label: 'Duplicate', leading: const Icon(LucideIcons.copy), - onPressed: widget.canDuplicate ? () => _duplicateStrategy() : null, - child: const Text('Duplicate'), + enabled: widget.canDuplicate, + onPressed: _duplicateStrategy, ), - ShadContextMenuItem( + _buildMenuItem( + label: 'Export', leading: const Icon(LucideIcons.upload), - onPressed: () => _exportStrategy(), - child: const Text('Export'), + onPressed: _exportStrategy, ), if (_canShare) - ShadContextMenuItem( + _buildMenuItem( + label: 'Share', leading: const Icon(LucideIcons.link2), onPressed: _showShareDialog, - child: const Text('Share'), ), - ShadContextMenuItem( + _buildMenuItem( + label: 'Delete', leading: Icon( LucideIcons.trash2, color: Settings.tacticalVioletTheme.destructive, ), - onPressed: widget.canDelete ? () => _showDeleteDialog() : null, - child: Text( - 'Delete', - style: TextStyle(color: Settings.tacticalVioletTheme.destructive), - ), + enabled: widget.canDelete, + onPressed: _showDeleteDialog, + textStyle: TextStyle(color: Settings.tacticalVioletTheme.destructive), ), ]; } + Widget _buildMenuItem({ + required String label, + required Widget leading, + required VoidCallback onPressed, + bool enabled = true, + TextStyle? textStyle, + }) { + void activate() { + _menuButtonController.hide(); + _rightClickMenuController.hide(); + onPressed(); + } + + return StrategyTileMenuActionSemantics( + label: '$label $_strategyName', + enabled: enabled, + onPressed: enabled ? activate : null, + child: ShadContextMenuItem( + leading: leading, + enabled: enabled, + onPressed: enabled ? activate : null, + child: Text(label, style: textStyle), + ), + ); + } + Future _openStrategy(BuildContext context) async { if (_isLoading) { return; @@ -293,3 +311,58 @@ class _StrategyTileState extends ConsumerState { ); } } + +class StrategyTileActionsButton extends StatelessWidget { + const StrategyTileActionsButton({ + super.key, + required this.strategyName, + required this.onPressed, + }); + + final String strategyName; + final VoidCallback onPressed; + + @override + Widget build(BuildContext context) { + final label = 'More actions for $strategyName'; + return Semantics( + label: label, + button: true, + onTap: onPressed, + excludeSemantics: true, + child: ShadIconButton.secondary( + width: 28, + height: 28, + onPressed: onPressed, + icon: const Icon(Icons.more_vert_outlined), + ), + ); + } +} + +class StrategyTileMenuActionSemantics extends StatelessWidget { + const StrategyTileMenuActionSemantics({ + super.key, + required this.label, + required this.enabled, + required this.onPressed, + required this.child, + }); + + final String label; + final bool enabled; + final VoidCallback? onPressed; + final Widget child; + + @override + Widget build(BuildContext context) { + return Semantics( + label: label, + button: true, + enabled: enabled, + onTap: onPressed, + excludeSemantics: true, + child: child, + ); + } +} diff --git a/lib/widgets/strategy_tile/strategy_tile_sections.dart b/lib/widgets/strategy_tile/strategy_tile_sections.dart index 6efa41cf..c16dfe2b 100644 --- a/lib/widgets/strategy_tile/strategy_tile_sections.dart +++ b/lib/widgets/strategy_tile/strategy_tile_sections.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/const/agents.dart'; import 'package:icarus/const/maps.dart'; import 'package:icarus/const/settings.dart'; @@ -42,25 +42,19 @@ class StrategyTileViewData { ); } - factory StrategyTileViewData.fromCloudSummary(CloudStrategySummary strategy) { - MapValue? mapValue; - for (final entry in Maps.mapNames.entries) { - if (entry.value == strategy.mapData) { - mapValue = entry.key; - break; - } - } - final attackLabel = strategy.attackLabel ?? 'Unknown'; + factory StrategyTileViewData.fromCloudEntry(CloudStrategyEntry entry) { + final strategy = entry.strategy; + final attackLabel = entry.attackLabel; return StrategyTileViewData( name: strategy.name, - mapName: _mapName(mapValue), + mapName: _mapName(strategy.mapData), attackLabel: attackLabel, attackColor: _attackColor(attackLabel), thumbnailAsset: - 'assets/maps/thumbnails/${strategy.mapData}_thumbnail.webp', - lastEditedLabel: _timeAgo(strategy.updatedAt), + 'assets/maps/thumbnails/${Maps.mapNames[strategy.mapData]}_thumbnail.webp', + lastEditedLabel: _timeAgo(strategy.lastEdited), agentTypes: const [], - cloudBadge: cloudBadgeKindForRole(strategy.role), + cloudBadge: cloudBadgeKindForRole(entry.role), ); } diff --git a/macos/Runner/Release.entitlements b/macos/Runner/Release.entitlements index 852fa1a4..a0463869 100644 --- a/macos/Runner/Release.entitlements +++ b/macos/Runner/Release.entitlements @@ -4,5 +4,9 @@ com.apple.security.app-sandbox + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + diff --git a/package.json b/package.json index 0b24d995..004a8778 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,10 @@ "type": "module", "private": true, "scripts": { + "audit:convex-contract": "node tool/audit_convex_contract.mjs", + "audit:convex-architecture": "fvm flutter test test/convex_architecture_test.dart", + "snapshot:convex-contract": "node tool/snapshot_convex_contract.mjs", + "snapshot:convex-contract:check": "node tool/snapshot_convex_contract.mjs --check", "test:convex": "vitest run --config vitest.config.ts" }, "devDependencies": { diff --git a/pubspec.lock b/pubspec.lock index d44386be..5457903a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -252,10 +252,9 @@ packages: convex_flutter: dependency: "direct main" description: - name: convex_flutter - sha256: db3bca4e3e6792eadadba9a662225ccb743c287f4550879f67885cd40a2198f2 - url: "https://pub.dev" - source: hosted + path: "third_party/convex_flutter" + relative: true + source: path version: "3.0.1" cross_file: dependency: "direct main" diff --git a/pubspec.yaml b/pubspec.yaml index 5854699d..f116d5bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,7 +40,8 @@ dependencies: pasteboard: ^0.4.0 desktop_updater: ^1.4.0 cryptography_plus: ^2.7.1 - convex_flutter: ^3.0.1 + convex_flutter: + path: third_party/convex_flutter supabase: ^2.10.2 supabase_flutter: ^2.12.0 win32_registry: ^2.1.0 diff --git a/test/cloud_ui_parity_helpers_test.dart b/test/cloud_ui_parity_helpers_test.dart index c211cc61..c15fdcd9 100644 --- a/test/cloud_ui_parity_helpers_test.dart +++ b/test/cloud_ui_parity_helpers_test.dart @@ -1,22 +1,25 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:icarus/collab/collab_models.dart'; import 'package:icarus/const/folder_icons.dart'; +import 'package:icarus/domain/folder.dart'; import 'package:icarus/providers/collab/strategy_capabilities_provider.dart'; -import 'package:icarus/providers/folder_provider.dart'; void main() { - test('cloud folder summary adapts to local folder model defaults', () { - final summary = CloudFolderSummary( - publicId: 'folder-1', + test('cloud folder values adapt to local folder model defaults', () { + final folder = Folder( + id: 'folder-1', name: 'Set Plays', - createdAt: DateTime(2026, 1, 1), - updatedAt: DateTime(2026, 1, 2), - parentFolderPublicId: 'parent-1', + dateCreated: DateTime(2026, 1, 1), + parentID: 'parent-1', + iconId: folderIconIdFromCloud( + iconId: null, + codePoint: null, + fontFamily: null, + fontPackage: null, + ), + color: folderColorFromWireName(null), ); - final folder = FolderProvider.cloudSummaryToFolder(summary); - expect(folder.id, 'folder-1'); expect(folder.name, 'Set Plays'); expect(folder.parentID, 'parent-1'); @@ -25,19 +28,21 @@ void main() { expect(folder.customColor, isNull); }); - test('cloud folder summary preserves icon and color metadata', () { - final summary = CloudFolderSummary( - publicId: 'folder-2', + test('cloud folder values preserve icon and color metadata', () { + final folder = Folder( + id: 'folder-2', name: 'Execs', - createdAt: DateTime(2026, 1, 1), - updatedAt: DateTime(2026, 1, 2), - iconId: FolderIconRegistry.duelistRoleId, - color: 'red', - customColorValue: 0xFF123456, + dateCreated: DateTime(2026, 1, 1), + iconId: folderIconIdFromCloud( + iconId: FolderIconRegistry.duelistRoleId, + codePoint: null, + fontFamily: null, + fontPackage: null, + ), + color: folderColorFromWireName('red'), + customColor: folderCustomColorFromCloud(0xFF123456), ); - final folder = FolderProvider.cloudSummaryToFolder(summary); - expect(folder.iconId, FolderIconRegistry.duelistRoleId); expect(folder.color, FolderColor.red); expect(folder.customColor, const Color(0xFF123456)); diff --git a/test/collab_sync_models_test.dart b/test/collab_sync_models_test.dart index 4be9357f..d84f345d 100644 --- a/test/collab_sync_models_test.dart +++ b/test/collab_sync_models_test.dart @@ -2,8 +2,10 @@ import 'package:flutter_test/flutter_test.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/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'; void main() { group('CloudCollabModeState', () { @@ -45,36 +47,316 @@ void main() { group('StrategyOp protocol', () { test('serializes record revision without a global sequence', () { - const op = StrategyOp( + const op = PageContentPatchOp( opId: 'op-1', - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.pageContent, - entityPublicId: 'page-1', - payload: {'settings': {}}, - expectedRevision: 4, + pagePublicId: 'page-1', + settings: {}, + expectedPageContentRevision: 4, ); final json = op.toConvexJson(); - expect(currentCloudProtocolVersion, 2); - expect(json['entityType'], 'pageContent'); - expect(json['expectedRevision'], 4); + expect(currentCloudProtocolVersion, 3); + expect(json['type'], 'pageContent.patch'); + expect(json['expectedPageContentRevision'], 4); expect(json.containsKey('expectedSequence'), isFalse); - expect(StrategyOp.fromJson(json).entityType, - StrategyOpEntityType.pageContent); + expect(StrategyOp.fromJson(json), isA()); }); - test('copyWith preserves identity', () { - const original = StrategyOp( + test('withOpId changes identity without changing typed intent', () { + const original = LineupPatchOp( opId: 'op-2', - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.lineup, - entityPublicId: 'lineup-1', + lineupPublicId: 'lineup-1', pagePublicId: 'page-1', + expectedLineupRevision: 3, ); - final updated = original.copyWith(expectedRevision: 9); - expect(updated.opId, original.opId); + final updated = original.withOpId('op-3'); + expect(updated.opId, 'op-3'); expect(updated.entityPublicId, original.entityPublicId); - expect(updated.expectedRevision, 9); + expect(updated.expectedRevision, 3); + expect(updated.type, StrategyOpType.lineupPatch); + }); + + test('every operation emits only its protocol-v3 fields', () { + const payload = {'value': 1}; + const ops = [ + StrategyPatchOp( + opId: '1', + payload: payload, + expectedStrategyRevision: 1, + ), + PageAddOp( + opId: '2', + pagePublicId: 'page', + payload: payload, + sortIndex: 2, + expectedStrategyRevision: 1, + ), + PagePatchOp( + opId: '3', + pagePublicId: 'page', + payload: payload, + expectedPageRevision: 3, + ), + PageDeleteOp( + opId: '4', + pagePublicId: 'page', + expectedStrategyRevision: 4, + ), + PageReorderOp( + opId: '5', + pagePublicId: 'page', + sortIndex: 5, + expectedStrategyRevision: 4, + ), + PageContentPatchOp( + opId: '6', + pagePublicId: 'page', + settings: payload, + expectedPageContentRevision: 6, + ), + ElementAddOp( + opId: '7', + elementPublicId: 'element', + pagePublicId: 'page', + payload: payload, + sortIndex: 7, + ), + ElementPatchOp( + opId: '8', + elementPublicId: 'element', + pagePublicId: 'other-page', + payload: payload, + sortIndex: 8, + expectedElementRevision: 8, + ), + ElementDeleteOp( + opId: '9', + elementPublicId: 'element', + pagePublicId: 'page', + expectedElementRevision: 9, + ), + ElementReorderOp( + opId: '10', + elementPublicId: 'element', + pagePublicId: 'page', + sortIndex: 10, + expectedElementRevision: 10, + ), + LineupAddOp( + opId: '11', + lineupPublicId: 'lineup', + pagePublicId: 'page', + payload: payload, + sortIndex: 11, + ), + LineupPatchOp( + opId: '12', + lineupPublicId: 'lineup', + pagePublicId: 'other-page', + payload: payload, + sortIndex: 12, + expectedLineupRevision: 12, + ), + LineupDeleteOp( + opId: '13', + lineupPublicId: 'lineup', + pagePublicId: 'page', + expectedLineupRevision: 13, + ), + LineupReorderOp( + opId: '14', + lineupPublicId: 'lineup', + pagePublicId: 'page', + sortIndex: 14, + expectedLineupRevision: 14, + ), + ]; + const expected = >[ + { + 'opId': '1', + 'type': 'strategy.patch', + 'payload': payload, + 'expectedStrategyRevision': 1, + }, + { + 'opId': '2', + 'type': 'page.add', + 'pagePublicId': 'page', + 'payload': payload, + 'sortIndex': 2, + 'expectedStrategyRevision': 1, + }, + { + 'opId': '3', + 'type': 'page.patch', + 'pagePublicId': 'page', + 'payload': payload, + 'expectedPageRevision': 3, + }, + { + 'opId': '4', + 'type': 'page.delete', + 'pagePublicId': 'page', + 'expectedStrategyRevision': 4, + }, + { + 'opId': '5', + 'type': 'page.reorder', + 'pagePublicId': 'page', + 'sortIndex': 5, + 'expectedStrategyRevision': 4, + }, + { + 'opId': '6', + 'type': 'pageContent.patch', + 'pagePublicId': 'page', + 'settings': payload, + 'expectedPageContentRevision': 6, + }, + { + 'opId': '7', + 'type': 'element.add', + 'elementPublicId': 'element', + 'pagePublicId': 'page', + 'payload': payload, + 'sortIndex': 7, + }, + { + 'opId': '8', + 'type': 'element.patch', + 'elementPublicId': 'element', + 'pagePublicId': 'other-page', + 'payload': payload, + 'sortIndex': 8, + 'expectedElementRevision': 8, + }, + { + 'opId': '9', + 'type': 'element.delete', + 'elementPublicId': 'element', + 'pagePublicId': 'page', + 'expectedElementRevision': 9, + }, + { + 'opId': '10', + 'type': 'element.reorder', + 'elementPublicId': 'element', + 'pagePublicId': 'page', + 'sortIndex': 10, + 'expectedElementRevision': 10, + }, + { + 'opId': '11', + 'type': 'lineup.add', + 'lineupPublicId': 'lineup', + 'pagePublicId': 'page', + 'payload': payload, + 'sortIndex': 11, + }, + { + 'opId': '12', + 'type': 'lineup.patch', + 'lineupPublicId': 'lineup', + 'pagePublicId': 'other-page', + 'payload': payload, + 'sortIndex': 12, + 'expectedLineupRevision': 12, + }, + { + 'opId': '13', + 'type': 'lineup.delete', + 'lineupPublicId': 'lineup', + 'pagePublicId': 'page', + 'expectedLineupRevision': 13, + }, + { + 'opId': '14', + 'type': 'lineup.reorder', + 'lineupPublicId': 'lineup', + 'pagePublicId': 'page', + 'sortIndex': 14, + 'expectedLineupRevision': 14, + }, + ]; + + for (var index = 0; index < ops.length; index += 1) { + final json = ops[index].toConvexJson(); + expect(json, expected[index]); + expect(json, isNot(contains('kind'))); + expect(json, isNot(contains('entityType'))); + expect(StrategyOp.fromJson(json).toConvexJson(), json); + } + }); + }); + + group('closed operation results', () { + test('decodes applied, noop, rejected, and failed variants', () { + expect( + OpAck.fromJson({ + 'opId': 'applied', + 'status': 'applied', + 'appliedRevision': 2, + }), + isA(), + ); + expect( + OpAck.fromJson({ + 'opId': 'noop', + 'status': 'noop', + 'currentRevision': 3, + }), + isA(), + ); + final rejected = OpAck.fromJson({ + 'opId': 'rejected', + 'status': 'rejected', + 'reason': 'revision_mismatch', + 'current': { + 'type': 'page', + 'revision': 4, + 'value': {'name': 'Current', 'isAttack': true, 'sortIndex': 0}, + }, + }); + expect(rejected, isA()); + expect(rejected.latestRevision, 4); + expect(rejected.latestPayload?['name'], 'Current'); + final failed = OpAck.fromJson({ + 'opId': 'failed', + 'status': 'failed', + 'code': 'INTERNAL_ERROR', + 'rawCode': 'NEW_SERVER_CODE', + 'message': 'Server failed safely', + }); + expect(failed, isA()); + expect((failed as FailedOpAck).rawCode, 'NEW_SERVER_CODE'); + expect(failed.isAck, isFalse); + }); + + test('decodes every closed rejection reason', () { + for (final reason in OpRejectionReason.values) { + final result = OpAck.fromJson({ + 'opId': reason.name, + 'status': 'rejected', + 'reason': reason.wireName, + }); + expect((result as RejectedOpAck).rejectionReason, reason); + } + }); + + test('rejects unknown result and current-snapshot discriminators', () { + expect( + () => OpAck.fromJson({'opId': 'x', 'status': 'future'}), + throwsFormatException, + ); + expect( + () => OpAck.fromJson({ + 'opId': 'x', + 'status': 'rejected', + 'reason': 'revision_mismatch', + 'current': {'type': 'future', 'revision': 1, 'value': {}}, + }), + throwsFormatException, + ); }); }); @@ -130,7 +412,7 @@ void main() { 'id': 'item-1', 'ability': { 'id': 'ability-1', - 'data': {'type': 'sova', 'index': 2}, + 'data': {'type': 'sova', 'index': 2.0}, 'position': {'dx': 30, 'dy': 40}, 'lineUpID': 'lineup-1', }, @@ -148,32 +430,114 @@ void main() { expect(group.items.single.notes, 'proof'); }); + test('ability info accepts Convex float64 integers and rejects fractions', + () { + const converter = AbilityInfoConverter(); + + expect( + converter.fromJson({ + 'type': 'sova', + 'index': 2.0, + }).index, + 2, + ); + expect( + () => converter.fromJson({ + 'type': 'sova', + 'index': 2.5, + }), + throwsFormatException, + ); + }); + + test('cloud export fails closed instead of dropping a malformed lineup', () { + final now = DateTime.utc(2026, 8, 29); + const strategyId = 'strategy-1'; + const pageId = 'page-1'; + final snapshot = RemoteFullStrategySnapshot( + header: RemoteStrategyHeader( + publicId: strategyId, + name: 'Cloud strategy', + mapData: 'ascent', + revision: 1, + createdAt: now, + updatedAt: now, + ), + pages: [ + RemoteFullPage( + page: RemotePage( + publicId: pageId, + strategyPublicId: strategyId, + name: 'Page 1', + sortIndex: 0, + isAttack: true, + revision: 1, + createdAt: now, + updatedAt: now, + ), + content: RemotePageContent( + revision: 1, + createdAt: now, + updatedAt: now, + ), + ), + ], + elementsByPage: const >{}, + lineupsByPage: const >{ + pageId: [ + RemoteLineup( + publicId: 'lineup-1', + strategyPublicId: strategyId, + pagePublicId: pageId, + payload: {'invalid': true}, + sortIndex: 0, + revision: 1, + deleted: false, + ), + ], + }, + assetsById: const {}, + ); + + expect( + () => StrategyImportExportService.strategyDataFromRemoteSnapshotForTest( + snapshot), + throwsA( + isA().having( + (error) => error.message, + 'message', + contains('Cloud lineup lineup-1 could not be exported'), + ), + ), + ); + }); + group('separate remote read models', () { - final header = RemoteStrategyHeader.fromJson({ - 'publicId': 'strat-1', - 'name': 'Cloud', - 'mapData': 'ascent', - 'revision': 3, - 'createdAt': 1, - 'updatedAt': 2, - 'themeOverridePalette': {'base': '#111111'}, - }); - final page = RemotePage.fromJson({ - 'publicId': 'page-1', - 'strategyPublicId': 'strat-1', - 'name': 'Page 1', - 'sortIndex': 0, - 'isAttack': true, - 'revision': 2, - 'createdAt': 1, - 'updatedAt': 2, - }); - final content = RemotePageContent.fromJson({ - 'settings': {'agentSize': 35.0}, - 'revision': 7, - 'createdAt': 1, - 'updatedAt': 2, - }); + final header = RemoteStrategyHeader( + publicId: 'strat-1', + name: 'Cloud', + mapData: 'ascent', + revision: 3, + createdAt: DateTime.fromMillisecondsSinceEpoch(1), + updatedAt: DateTime.fromMillisecondsSinceEpoch(2), + themeOverridePalette: const {'base': '#111111'}, + ); + final page = RemotePage( + publicId: 'page-1', + strategyPublicId: 'strat-1', + name: 'Page 1', + sortIndex: 0, + isAttack: true, + revision: 2, + createdAt: DateTime.fromMillisecondsSinceEpoch(1), + updatedAt: DateTime.fromMillisecondsSinceEpoch(2), + ); + final content = RemotePageContent( + settings: const {'agentSize': 35.0}, + revision: 7, + createdAt: DateTime.fromMillisecondsSinceEpoch(1), + updatedAt: DateTime.fromMillisecondsSinceEpoch(2), + ); test('shell carries descriptors but no page content', () { final shell = RemoteStrategyShell(header: header, pages: [page]); @@ -218,31 +582,34 @@ void main() { }); group('RemoteImageAsset', () { - test('parses R2 metadata', () { - final asset = RemoteImageAsset.fromJson({ - 'publicId': 'asset-1', - 'provider': 'r2', - 'uploadStatus': 'active', - 'fileExtension': '.png', - 'byteSize': 42, - 'uploadedAt': 1700000000000, - 'url': 'https://media.example.com/asset-1.png', - }); + test('retains typed R2 metadata', () { + final asset = RemoteImageAsset( + publicId: 'asset-1', + provider: 'r2', + uploadStatus: 'active', + fileExtension: '.png', + width: null, + height: null, + byteSize: 42, + uploadedAt: DateTime.fromMillisecondsSinceEpoch(1700000000000), + url: 'https://media.example.com/asset-1.png', + legacyStoragePath: null, + ); expect(asset.provider, 'r2'); expect(asset.byteSize, 42); }); }); - test('CloudImageUploadIntent parses upload headers', () { - final intent = CloudImageUploadIntent.fromJson({ - 'provider': 'r2', - 'uploadId': 'upload-1', - 'objectKey': 'strategies/s/images/a.png', - 'uploadUrl': 'https://example.invalid/key', - 'requiredHeaders': {'Content-Type': 'image/png'}, - 'expiresAt': 1700000000000, - 'maxBytes': 1024, - }); + test('CloudImageUploadIntent retains typed upload headers', () { + final intent = CloudImageUploadIntent( + provider: 'r2', + uploadId: 'upload-1', + objectKey: 'strategies/s/images/a.png', + uploadUrl: 'https://example.invalid/key', + requiredHeaders: const {'Content-Type': 'image/png'}, + expiresAt: DateTime.fromMillisecondsSinceEpoch(1700000000000), + maxBytes: 1024, + ); expect(intent.requiredHeaders['Content-Type'], 'image/png'); expect(intent.maxBytes, 1024); }); diff --git a/test/convex_architecture_test.dart b/test/convex_architecture_test.dart new file mode 100644 index 00000000..45e74211 --- /dev/null +++ b/test/convex_architecture_test.dart @@ -0,0 +1,138 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + final repositoryRoot = Directory.current; + + test('generated Convex API stays inside the collaboration boundary', () { + final violations = []; + for (final file in _dartFiles(repositoryRoot, 'lib')) { + final relativePath = _relativePath(repositoryRoot, file); + if (relativePath.startsWith('lib/collab/')) continue; + final source = file.readAsStringSync(); + if (source.contains('collab/generated/') || + source.contains('IcarusConvexApi') || + source.contains('ConvexOptional<')) { + violations.add(relativePath); + } + } + + expect( + violations, + isEmpty, + reason: + 'Generated client imports and types may only appear in lib/collab.', + ); + }); + + test('collaboration data layer does not import Flutter UI libraries', () { + final violations = []; + for (final file in _dartFiles(repositoryRoot, 'lib/collab')) { + final source = file.readAsStringSync(); + if (source.contains("package:flutter/material.dart") || + source.contains("package:flutter/widgets.dart")) { + violations.add(_relativePath(repositoryRoot, file)); + } + } + + expect( + violations, + isEmpty, + reason: 'The collaboration data layer must not import Flutter UI.', + ); + }); + + test('public route strings stay in generated or transport code', () { + final spec = jsonDecode( + File('${repositoryRoot.path}/convex/function_spec.json') + .readAsStringSync(), + ) as Map; + final routes = (spec['functions'] as List) + .whereType() + .where((entry) => (entry['visibility'] as Map?)?['kind'] == 'public') + .map((entry) => entry['identifier'] as String) + .map((identifier) => identifier.replaceFirst('.js:', ':')) + .toSet(); + final violations = []; + + for (final file in _dartFiles(repositoryRoot, 'lib')) { + final relativePath = _relativePath(repositoryRoot, file); + if (relativePath.startsWith('lib/collab/generated/') || + relativePath.startsWith('lib/collab/transport/') || + relativePath.startsWith('lib/collab/src/')) { + continue; + } + final source = file.readAsStringSync(); + for (final route in routes) { + final quotedRoute = RegExp("['\"]${RegExp.escape(route)}['\"]"); + if (quotedRoute.hasMatch(source)) { + violations.add('$relativePath: $route'); + } + } + } + + expect( + violations, + isEmpty, + reason: 'Call generated modules instead of spelling Convex routes.', + ); + }); + + test('legacy JSON repository boundary stays deleted', () { + final collabSource = _dartFiles(repositoryRoot, 'lib/collab') + .map((file) => file.readAsStringSync()) + .join('\n'); + for (final legacySymbol in const [ + '_decodeJsonPayload', + '_decodeObjectList', + 'watchFoldersForParent', + 'class CloudFolderSummary', + 'class CloudStrategySummary', + 'factory CloudFolderSummary.fromJson', + 'factory CloudStrategySummary.fromJson', + 'factory CloudImageUploadIntent.fromJson', + 'factory RemoteStrategyHeader.fromJson', + 'factory RemotePage.fromJson', + 'factory RemotePageContent.fromJson', + 'factory RemoteElement.fromJson', + 'factory RemoteLineup.fromJson', + 'factory RemoteImageAsset.fromJson', + ]) { + expect(collabSource, isNot(contains(legacySymbol)), reason: legacySymbol); + } + }); + + test('raw Convex client calls stay inside platform adapters', () { + final violations = []; + final directCall = RegExp( + r'\b(?:ConvexClient\.instance|_client|client)\s*\.\s*' + r'(?:query|mutation|action|subscribe)\s*\(', + ); + for (final file in _dartFiles(repositoryRoot, 'lib')) { + final relativePath = _relativePath(repositoryRoot, file); + if (relativePath.startsWith('lib/collab/src/')) continue; + if (directCall.hasMatch(file.readAsStringSync())) { + violations.add(relativePath); + } + } + + expect( + violations, + isEmpty, + reason: 'Raw calls belong only in the native and web adapters.', + ); + }); +} + +Iterable _dartFiles(Directory root, String relativeDirectory) { + return Directory('${root.path}/$relativeDirectory') + .listSync(recursive: true) + .whereType() + .where((file) => file.path.endsWith('.dart')); +} + +String _relativePath(Directory root, File file) { + return file.path.substring(root.path.length + 1).replaceAll('\\', '/'); +} diff --git a/test/convex_generated_client_test.dart b/test/convex_generated_client_test.dart new file mode 100644 index 00000000..882c6f20 --- /dev/null +++ b/test/convex_generated_client_test.dart @@ -0,0 +1,372 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/convex_payload_codecs.dart'; +import 'package:icarus/collab/generated/generated.dart'; +import 'package:icarus/collab/src/convex_client_types.dart'; +import 'package:icarus/collab/transport/convex_transport.dart'; +import 'package:icarus/collab/transport/convex_transport_adapter.dart'; + +void main() { + test('generated query fetch and watch decode the same typed folder tree', + () async { + final transport = _FakeTransport(result: _folderTreeValue()); + final api = IcarusConvexApi(transport); + final query = api.folders.listTree( + scope: const ConvexOptional.present(FoldersListTreeArgsScope.all), + ); + + final fetched = await query.fetch(); + final watched = await query.watch().first; + + expect(fetched.single.publicId, 'folder-1'); + expect(watched.single.role, FoldersListTreeResultItemRole.owner); + expect(transport.lastName, 'folders:listTree'); + expect( + (transport.lastArgs!.value['scope'] as ConvexString).value, + 'all', + ); + }); + + test('generated decoder reports the full endpoint and field path', () { + final invalid = _folderTreeValue(role: 'future-role'); + expect( + () => decodeFoldersListTreeResult(invalid), + throwsA( + isA().having( + (error) => error.path, + 'path', + 'folders.js:listTree.returns[0].role', + ), + ), + ); + }); + + test('unknown server error code and structured data are preserved', () async { + final data = ConvexObject({'retryable': const ConvexBoolean(true)}); + final api = IcarusConvexApi( + _FakeTransport( + result: const ConvexNull(), + error: ConvexTransportError( + rawCode: 'FUTURE_SERVER_CODE', + message: 'Try again later', + data: data, + ), + ), + ); + + await expectLater( + api.health.ping().fetch(), + throwsA( + isA() + .having((error) => error.code, 'code', ConvexErrorCode.unknown) + .having( + (error) => error.rawCode, + 'rawCode', + 'FUTURE_SERVER_CODE', + ) + .having((error) => error.data, 'data', same(data)), + ), + ); + }); + + test('normalized Convex values round-trip fixed-seed nested values', () { + final random = Random(0x1ca405); + for (var index = 0; index < 250; index += 1) { + final source = _randomJsonValue(random, 4); + final decoded = ConvexValue.fromDart(source).toDart(); + expect(jsonEncode(decoded), jsonEncode(source), + reason: 'seed item $index'); + } + + final bytes = Uint8List.fromList([0, 1, 127, 128, 255]); + final decodedBytes = ConvexValue.fromDart(bytes).toDart() as Uint8List; + expect(decodedBytes, bytes); + expect( + (ConvexValue.fromDart(double.nan).toDart() as double).isNaN, + isTrue, + ); + expect( + ConvexValue.fromDart(double.infinity).toDart(), + double.infinity, + ); + }); + + test('annotated payload codecs enforce their exact server tag', () { + final payload = cloudElementPayload( + kind: 'agent', + data: { + 'id': 'agent-1', + 'position': [12.5, 8.0], + }, + ); + const codec = AgentConvexCodec(); + + expect(codec.decode(codec.encode(payload)), payload); + expect( + () => codec.encode({...payload, 'kind': 'drawing'}), + throwsFormatException, + ); + }); + + test('platform transport normalizes native and web value shapes identically', + () async { + final raw = { + 'nullValue': null, + 'integerValue': 9223372036854775807, + 'bigintValue': BigInt.parse('-9223372036854775808'), + 'floatValue': -0.0, + 'bytesValue': Uint8List.fromList([0, 127, 255]), + 'nested': [ + true, + {'present': 'yes'}, + ], + }; + final nativeLike = PlatformConvexTransport( + _ScriptedValueSource(result: raw), + ); + final webLike = PlatformConvexTransport( + _ScriptedValueSource(result: Map.from(raw)), + ); + + final nativeValue = await nativeLike.query( + 'fixture:values', + ConvexObject(const {}), + ); + final webValue = await webLike.query( + 'fixture:values', + ConvexObject(const {}), + ); + + expect(_describeConvexValue(nativeValue), _describeConvexValue(webValue)); + final object = nativeValue as ConvexObject; + expect(object.value.containsKey('omitted'), isFalse); + expect( + (object.value['bytesValue'] as ConvexBytes).value, + Uint8List.fromList([0, 127, 255]), + ); + expect( + (object.value['floatValue'] as ConvexFloat).value.isNegative, + isTrue, + ); + }); + + test('recoverable function error does not close a generated watch', () async { + final source = _ScriptedValueSource( + result: _folderTreeValue().toDart(), + subscriptionEvents: [ + _folderTreeValue(name: 'Before error').toDart(), + const ConvexClientFunctionError( + rawCode: 'CONFLICT', + message: 'retryable conflict', + data: {'code': 'CONFLICT', 'revision': 4}, + ), + _folderTreeValue(name: 'After error').toDart(), + ], + ); + final api = IcarusConvexApi(PlatformConvexTransport(source)); + final names = []; + final errors = []; + final receivedSecondValue = Completer(); + final subscription = api.folders.listTree().watch().listen( + (folders) { + names.add(folders.single.name); + if (names.length == 2) receivedSecondValue.complete(); + }, + onError: (Object error) => errors.add(error), + ); + + await receivedSecondValue.future.timeout(const Duration(seconds: 2)); + await subscription.cancel(); + + expect(names, ['Before error', 'After error']); + expect(errors, [isA()]); + final error = errors.single as ConvexFunctionException; + expect(error.code, ConvexErrorCode.conflict); + expect(error.rawCode, 'CONFLICT'); + expect(source.handle.cancelled, isTrue); + }); + + test('normalization failure closes a generated watch', () async { + final source = _ScriptedValueSource( + result: _folderTreeValue().toDart(), + subscriptionEvents: [DateTime.utc(2026), _folderTreeValue().toDart()], + ); + final api = IcarusConvexApi(PlatformConvexTransport(source)); + final errors = []; + final done = Completer(); + + api.folders.listTree().watch().listen( + (_) => fail('A contract-failed watch must not emit another value'), + onError: (Object error) => errors.add(error), + onDone: done.complete, + ); + await done.future.timeout(const Duration(seconds: 2)); + + expect(errors, [isA()]); + expect(source.handle.cancelled, isTrue); + }); +} + +ConvexArray _folderTreeValue({ + String role = 'owner', + String name = 'Defaults', +}) => + ConvexArray([ + ConvexObject({ + 'color': const ConvexNull(), + 'createdAt': const ConvexInteger(1), + 'customColorValue': const ConvexNull(), + 'iconCodePoint': const ConvexNull(), + 'iconFontFamily': const ConvexNull(), + 'iconFontPackage': const ConvexNull(), + 'iconId': const ConvexNull(), + 'name': ConvexString(name), + 'parentFolderPublicId': const ConvexNull(), + 'publicId': const ConvexString('folder-1'), + 'role': ConvexString(role), + 'updatedAt': const ConvexInteger(2), + }), + ]); + +Object? _describeConvexValue(ConvexValue value) => switch (value) { + ConvexNull() => 'null', + ConvexBoolean(:final value) => ['boolean', value], + ConvexInteger(:final value) => ['integer', value], + ConvexFloat(:final value) => [ + 'float', + value == 0 && value.isNegative ? '-0' : value, + ], + ConvexBigInt(:final value) => ['bigint', value.toString()], + ConvexString(:final value) => ['string', value], + ConvexBytes(:final value) => ['bytes', ...value], + ConvexArray(:final value) => [ + 'array', + ...value.map(_describeConvexValue), + ], + ConvexObject(:final value) => { + 'object': { + for (final entry in value.entries) + entry.key: _describeConvexValue(entry.value), + }, + }, + }; + +Object? _randomJsonValue(Random random, int depth) { + if (depth == 0) { + return switch (random.nextInt(4)) { + 0 => null, + 1 => random.nextBool(), + 2 => random.nextInt(100000), + _ => 'value-${random.nextInt(100000)}', + }; + } + return switch (random.nextInt(6)) { + 0 => null, + 1 => random.nextBool(), + 2 => random.nextInt(100000), + 3 => random.nextDouble() * 1000, + 4 => [ + for (var index = 0; index < random.nextInt(5); index += 1) + _randomJsonValue(random, depth - 1), + ], + _ => { + for (var index = 0; index < random.nextInt(5); index += 1) + 'key-$index': _randomJsonValue(random, depth - 1), + }, + }; +} + +final class _FakeTransport implements ConvexTransport { + _FakeTransport({required this.result, this.error}); + + final ConvexValue result; + final ConvexTransportError? error; + String? lastName; + ConvexObject? lastArgs; + + Future _respond(String name, ConvexObject args) async { + lastName = name; + lastArgs = args; + final failure = error; + if (failure != null) throw failure; + return result; + } + + @override + Future action(String name, ConvexObject args) => + _respond(name, args); + + @override + Future mutation(String name, ConvexObject args) => + _respond(name, args); + + @override + Future query(String name, ConvexObject args) => + _respond(name, args); + + @override + Stream subscribe(String name, ConvexObject args) async* { + yield await _respond(name, args); + } +} + +final class _ScriptedValueSource implements ConvexClientValueSource { + _ScriptedValueSource({ + required this.result, + this.subscriptionEvents = const [], + }); + + final Object? result; + final List subscriptionEvents; + final _TestSubscriptionHandle handle = _TestSubscriptionHandle(); + + @override + Future actionValue({ + required String name, + required Map args, + }) async => + result; + + @override + Future mutationValue({ + required String name, + required Map args, + }) async => + result; + + @override + Future queryValue(String name, Map args) async => + result; + + @override + Future subscribeValue({ + required String name, + required Map args, + required void Function(Object? value) onUpdate, + required void Function(ConvexClientFunctionError error) onError, + }) async { + scheduleMicrotask(() { + for (final event in subscriptionEvents) { + if (handle.cancelled) return; + if (event is ConvexClientFunctionError) { + onError(event); + } else { + onUpdate(event); + } + } + }); + return handle; + } +} + +final class _TestSubscriptionHandle implements SubscriptionHandle { + bool cancelled = false; + + @override + void cancel() => cancelled = true; +} diff --git a/test/durable_strategy_outbox_version_test.dart b/test/durable_strategy_outbox_version_test.dart new file mode 100644 index 00000000..9c2b5a3b --- /dev/null +++ b/test/durable_strategy_outbox_version_test.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:hive_ce/hive.dart'; +import 'package:icarus/collab/durable_strategy_outbox.dart'; +import 'package:icarus/const/hive_boxes.dart'; + +void main() { + test('development outbox is cleared exactly once for record version 2', + () async { + final directory = await Directory.systemTemp.createTemp('icarus-outbox-'); + addTearDown(() async { + await Hive.close(); + await directory.delete(recursive: true); + }); + Hive.init(directory.path); + final box = await Hive.openBox(HiveBoxNames.strategyOutboxBox); + await box.put('legacy-work', {'outboxVersion': 1}); + + await prepareDurableStrategyOutbox(); + + expect(box.keys, [durableOutboxVersionKey]); + expect(box.get(durableOutboxVersionKey), durableOutboxRecordVersion); + + await box.put('v2-work', {'outboxVersion': durableOutboxRecordVersion}); + await prepareDurableStrategyOutbox(); + expect(box.containsKey('v2-work'), isTrue); + }); +} diff --git a/test/lineup_add_item_interaction_test.dart b/test/lineup_add_item_interaction_test.dart index 42fafa84..800b24b2 100644 --- a/test/lineup_add_item_interaction_test.dart +++ b/test/lineup_add_item_interaction_test.dart @@ -375,6 +375,56 @@ void main() { expect(resizedAbilityTopLeft.dy, closeTo(expectedAbilityTopLeft.dy, 0.001)); }); + testWidgets('persistent lineup markers remain draggable after completion', + (tester) async { + final container = _createContainer(); + final group = _breachGroup(); + container.read(lineUpProvider.notifier).addGroup(group); + + CoordinateSystem(playAreaSize: const Size(900, 600)); + await _pumpHarness( + tester, + container: container, + child: const SizedBox( + width: 900, + height: 600, + child: LineUpOverlay(), + ), + ); + + final abilityFinder = + find.byKey(const ValueKey('lineup-ability-drag-breach-item')); + final initialAbilityTopLeft = tester.getTopLeft(abilityFinder); + const abilityDelta = Offset(-55, 65); + await tester.drag(abilityFinder, abilityDelta); + await tester.pump(); + + var updated = container.read(lineUpProvider).groups.single; + final expectedAbilityPosition = CoordinateSystem.instance + .screenToCoordinate(initialAbilityTopLeft + abilityDelta); + expect( + updated.items.single.ability.position.dx, + closeTo(expectedAbilityPosition.dx, 0.001), + ); + expect( + updated.items.single.ability.position.dy, + closeTo(expectedAbilityPosition.dy, 0.001), + ); + + final agentFinder = + find.byKey(const ValueKey('lineup-agent-drag-breach-group')); + final initialAgentTopLeft = tester.getTopLeft(agentFinder); + const agentDelta = Offset(70, 45); + await tester.drag(agentFinder, agentDelta); + await tester.pump(); + + updated = container.read(lineUpProvider).groups.single; + final expectedAgentPosition = CoordinateSystem.instance + .screenToCoordinate(initialAgentTopLeft + agentDelta); + expect(updated.agent.position.dx, closeTo(expectedAgentPosition.dx, 0.001)); + expect(updated.agent.position.dy, closeTo(expectedAgentPosition.dy, 0.001)); + }); + testWidgets('locked add-item mode rejects dragging a different agent', (tester) async { final container = _createContainer(); diff --git a/test/macos_release_entitlements_test.dart b/test/macos_release_entitlements_test.dart new file mode 100644 index 00000000..59237842 --- /dev/null +++ b/test/macos_release_entitlements_test.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:flutter_test/flutter_test.dart'; + +void main() { + test('release sandbox permits cloud access and user-selected exports', () { + final entitlements = + File('macos/Runner/Release.entitlements').readAsStringSync(); + bool isEnabled(String key) => RegExp( + '${RegExp.escape(key)}\\s*', + ).hasMatch(entitlements); + + expect(isEnabled('com.apple.security.network.client'), isTrue); + expect( + isEnabled('com.apple.security.files.user-selected.read-write'), + isTrue, + ); + }); +} diff --git a/test/providers/auth_provider_test.dart b/test/providers/auth_provider_test.dart index 29c88c85..3a2dd076 100644 --- a/test/providers/auth_provider_test.dart +++ b/test/providers/auth_provider_test.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/collab/convex_client.dart'; import 'package:icarus/const/app_provider_container.dart'; import 'package:icarus/providers/auth_provider.dart'; import 'package:icarus/providers/in_app_debug_provider.dart'; @@ -311,7 +312,11 @@ void main() { test('real unauthenticated error still creates auth incident', () async { supabaseApi.currentSession = fakeSession(); - convexApi.mutationError = Exception('{"code":"UNAUTHENTICATED"}'); + convexApi.mutationError = const ConvexClientFunctionError( + rawCode: 'UNAUTHENTICATED', + message: 'Authentication required', + data: null, + ); final container = ProviderContainer(); addTearDown(container.dispose); @@ -467,16 +472,12 @@ class FakeConvexApi implements AuthProviderConvexApi { } @override - Future mutation({ - required String name, - required Map args, - }) async { + Future ensureCurrentUser() async { mutationCalls += 1; - lastMutationName = name; + lastMutationName = 'users:ensureCurrentUser'; if (mutationError case final Object error?) { throw error; } - return '{}'; } @override diff --git a/test/providers/cloud_migration_provider_test.dart b/test/providers/cloud_migration_provider_test.dart index 239dc376..05fa1ea2 100644 --- a/test/providers/cloud_migration_provider_test.dart +++ b/test/providers/cloud_migration_provider_test.dart @@ -125,7 +125,7 @@ class FakeCloudMigrationApi implements CloudMigrationApi { required List ops, }) async { return [ - for (final op in ops) OpAck(opId: op.opId, status: 'ack'), + for (final op in ops) NoopOpAck(opId: op.opId), ]; } } diff --git a/test/providers/remote_library_provider_test.dart b/test/providers/remote_library_provider_test.dart new file mode 100644 index 00000000..7ee0da2b --- /dev/null +++ b/test/providers/remote_library_provider_test.dart @@ -0,0 +1,208 @@ +import 'dart:async'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; +import 'package:icarus/collab/convex_strategy_repository.dart'; +import 'package:icarus/collab/generated/generated.dart'; +import 'package:icarus/collab/transport/convex_transport.dart'; +import 'package:icarus/const/maps.dart'; +import 'package:icarus/domain/folder.dart'; +import 'package:icarus/providers/auth_provider.dart'; +import 'package:icarus/providers/collab/remote_library_provider.dart'; + +void main() { + test('repository maps the typed folder tree into Icarus folders', () async { + final transport = _RecordingTransport(); + final repository = ConvexStrategyRepository(IcarusConvexApi(transport)); + final firstTree = repository.watchAllFolders().first; + await Future.delayed(Duration.zero); + + transport.emitSubscription( + 'folders:listTree', + ConvexValue.fromDart([ + { + 'publicId': 'folder-1', + 'name': 'Defaults', + 'iconId': 7, + 'iconCodePoint': 0xe2c7, + 'iconFontFamily': 'MaterialIcons', + 'iconFontPackage': null, + 'color': 'blue', + 'customColorValue': 0xff123456, + 'parentFolderPublicId': null, + 'createdAt': 1700000000000, + 'updatedAt': 1700000001000, + 'role': 'owner', + }, + ]), + ); + + final folders = await firstTree; + expect(transport.subscriptionCount['folders:listTree'], 1); + expect(transport.lastArgs['folders:listTree']?.toDart(), {'scope': 'all'}); + expect(folders.single.folder.id, 'folder-1'); + expect(folders.single.folder.iconId, 7); + expect(folders.single.folder.color, FolderColor.blue); + expect(folders.single.folder.customColor?.toARGB32(), 0xff123456); + expect(folders.single.role, 'owner'); + }); + + test('repository maps typed strategy rows into Icarus strategies', () async { + final transport = _RecordingTransport(); + final repository = ConvexStrategyRepository(IcarusConvexApi(transport)); + final firstList = repository.watchStrategiesForFolder(null).first; + await Future.delayed(Duration.zero); + + transport.emitSubscription( + 'strategies:listForFolder', + ConvexValue.fromDart([ + { + 'publicId': 'strategy-1', + 'name': 'A Split', + 'mapData': 'ascent', + 'folderPublicId': null, + 'revision': 4, + 'createdAt': 1700000000000, + 'updatedAt': 1700000001000, + 'themeProfileId': null, + 'themeOverridePalette': null, + 'role': 'owner', + 'attackLabel': 'Attack', + }, + ]), + ); + + final entry = (await firstList).single; + expect(entry.strategy.id, 'strategy-1'); + expect(entry.strategy.mapData, MapValue.ascent); + expect(entry.strategy.folderID, isNull); + expect( + entry.strategy.lastEdited, + DateTime.fromMillisecondsSinceEpoch(1700000001000), + ); + expect(entry.revision, 4); + expect(entry.role, 'owner'); + expect(entry.attackLabel, 'Attack'); + }); + + test('folder views share one cached tree subscription', () async { + final repository = _CountingRepository(); + final container = ProviderContainer( + overrides: [ + authProvider.overrideWith(_ReadyAuthProvider.new), + convexStrategyRepositoryProvider.overrideWithValue(repository), + ], + ); + addTearDown(container.dispose); + + final treeSubscription = container.listen( + cloudFolderTreeProvider, + (_, __) {}, + fireImmediately: true, + ); + final allSubscription = container.listen( + cloudAllFoldersProvider, + (_, __) {}, + fireImmediately: true, + ); + final childrenSubscription = container.listen( + cloudFoldersProvider, + (_, __) {}, + fireImmediately: true, + ); + addTearDown(treeSubscription.close); + addTearDown(allSubscription.close); + addTearDown(childrenSubscription.close); + await Future.delayed(Duration.zero); + + expect(repository.folderWatchCount, 1); + + repository.folders.add([ + ( + folder: Folder( + id: 'owned-root', + name: 'Owned', + dateCreated: DateTime(2026), + ), + role: 'owner', + ), + ( + folder: Folder( + id: 'shared-root', + name: 'Shared', + dateCreated: DateTime(2026), + ), + role: 'editor', + ), + ]); + for (var i = 0; + i < 20 && container.read(cloudFoldersProvider).valueOrNull == null; + i++) { + await Future.delayed(const Duration(milliseconds: 1)); + } + + expect( + container + .read(cloudFoldersProvider) + .valueOrNull + ?.map((entry) => entry.folder.id), + ['owned-root'], + ); + }); +} + +class _ReadyAuthProvider extends AuthProvider { + @override + AppAuthState build() => const AppAuthState( + isLoading: false, + isAuthenticated: true, + isConvexUserReady: true, + convexAuthStatus: ConvexAuthStatus.ready, + user: null, + ); +} + +class _CountingRepository extends ConvexStrategyRepository { + _CountingRepository() : super(IcarusConvexApi(_RecordingTransport())); + + final folders = StreamController>.broadcast(); + int folderWatchCount = 0; + + @override + Stream> watchAllFolders() { + folderWatchCount += 1; + return folders.stream; + } +} + +class _RecordingTransport implements ConvexTransport { + final subscriptionCount = {}; + final lastArgs = {}; + final _subscriptions = >{}; + + @override + Future action(String name, ConvexObject args) => + throw UnimplementedError(); + + @override + Future mutation(String name, ConvexObject args) => + throw UnimplementedError(); + + @override + Future query(String name, ConvexObject args) => + throw UnimplementedError(); + + @override + Stream subscribe(String name, ConvexObject args) { + subscriptionCount.update(name, (count) => count + 1, ifAbsent: () => 1); + lastArgs[name] = args; + return _subscriptions + .putIfAbsent(name, StreamController.broadcast) + .stream; + } + + void emitSubscription(String name, ConvexValue value) { + _subscriptions[name]!.add(value); + } +} diff --git a/test/strategy_op_queue_provider_test.dart b/test/strategy_op_queue_provider_test.dart index b45114e2..3303772a 100644 --- a/test/strategy_op_queue_provider_test.dart +++ b/test/strategy_op_queue_provider_test.dart @@ -51,16 +51,36 @@ void main() { String value = 'a', StrategyOpKind kind = StrategyOpKind.patch, }) { - return StrategyOp( - opId: opId, - kind: kind, - entityType: StrategyOpEntityType.element, - entityPublicId: elementId, - pagePublicId: 'page-1', - payload: {'value': value}, - sortIndex: 0, - expectedRevision: kind == StrategyOpKind.add ? null : 1, - ); + return switch (kind) { + StrategyOpKind.add => ElementAddOp( + opId: opId, + elementPublicId: elementId, + pagePublicId: 'page-1', + payload: {'value': value}, + sortIndex: 0, + ), + StrategyOpKind.patch => ElementPatchOp( + opId: opId, + elementPublicId: elementId, + pagePublicId: 'page-1', + payload: {'value': value}, + sortIndex: 0, + expectedElementRevision: 1, + ), + StrategyOpKind.delete => ElementDeleteOp( + opId: opId, + elementPublicId: elementId, + pagePublicId: 'page-1', + expectedElementRevision: 1, + ), + StrategyOpKind.reorder => ElementReorderOp( + opId: opId, + elementPublicId: elementId, + pagePublicId: 'page-1', + sortIndex: 0, + expectedElementRevision: 1, + ), + }; } DurableOutboxRecord record({ @@ -105,11 +125,9 @@ void main() { () async { final notifier = start(); await notifier.enqueue( - const StrategyOp( + const PageAddOp( opId: 'add-page', - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.page, - entityPublicId: 'page-2', + pagePublicId: 'page-2', payload: { 'name': 'Execute', 'isAttack': true, @@ -120,7 +138,7 @@ void main() { }, }, sortIndex: 1, - expectedRevision: 4, + expectedStrategyRevision: 4, ), flushImmediately: false, ); @@ -216,6 +234,16 @@ void main() { expect(store.values, contains('broken')); }); + test('protocol-v2 outbox records fail closed instead of converting', () { + final legacy = record(status: DurableOutboxStatus.queued).toJson() + ..['outboxVersion'] = 1; + + expect( + () => DurableOutboxRecord.fromJson(legacy), + throwsA(isA()), + ); + }); + test('reconciliation replaces rejected immutable opId before removal', () async { final saved = record(status: DurableOutboxStatus.attention); @@ -297,14 +325,13 @@ void main() { strategyPublicId: 'strategy-1', entityKey: key, pending: const PendingOp( - op: StrategyOp( + op: ElementAddOp( opId: 'restore-op', - kind: StrategyOpKind.add, - entityType: StrategyOpEntityType.element, - entityPublicId: 'element-1', + elementPublicId: 'element-1', pagePublicId: 'page-1', payload: {'value': 'restore me'}, - expectedRevision: 2, + sortIndex: 0, + expectedElementRevision: 2, ), clientId: 'stable-client', ), @@ -386,9 +413,115 @@ void main() { await notifier.enqueue(elementOp(opId: 'patch', value: 'b')); final pending = container!.read(strategyOpQueueProvider).pending.single; expect(pending.op.kind, StrategyOpKind.add); - expect(pending.op.opId, 'op-1'); + expect(pending.op.opId, isNot(anyOf('op-1', 'patch'))); expect(pending.op.payload, {'value': 'b'}); expect(store.values, hasLength(1)); + expect((store.values.values.single as Map)['opId'], pending.op.opId); + }); + + test('byte-for-byte equivalent work keeps its durable op ID', () async { + final notifier = start(); + await notifier.enqueue(elementOp(), flushImmediately: false); + await notifier.enqueue( + elementOp(opId: 'unused-equivalent-id'), + flushImmediately: false, + ); + + final pending = container!.read(strategyOpQueueProvider).pending.single; + expect(pending.op.opId, 'op-1'); + expect((store.values.values.single as Map)['opId'], 'op-1'); + }); + + test('patch replacement gets a new durable op ID', () async { + final notifier = start(); + await notifier.enqueue(elementOp(), flushImmediately: false); + await notifier.enqueue( + elementOp(opId: 'desired-patch', value: 'b'), + flushImmediately: false, + ); + + final pending = container!.read(strategyOpQueueProvider).pending.single; + expect(pending.op.opId, isNot(anyOf('op-1', 'desired-patch'))); + expect(pending.op.payload, {'value': 'b'}); + expect((store.values.values.single as Map)['opId'], pending.op.opId); + }); + + test('reorder replacement gets a new durable op ID', () async { + final notifier = start(); + const first = PageReorderOp( + opId: 'reorder-a', + pagePublicId: 'page-1', + sortIndex: 1, + expectedStrategyRevision: 2, + ); + const second = PageReorderOp( + opId: 'reorder-b', + pagePublicId: 'page-1', + sortIndex: 3, + expectedStrategyRevision: 2, + ); + await notifier.enqueue(first, flushImmediately: false); + await notifier.enqueue(second, flushImmediately: false); + + final pending = container!.read(strategyOpQueueProvider).pending.single; + expect(pending.op.opId, isNot(anyOf('reorder-a', 'reorder-b'))); + expect(pending.op.sortIndex, 3); + }); + + test('delete cancels a queued add and removes its durable record', + () async { + final notifier = start(); + await notifier.enqueue( + elementOp(kind: StrategyOpKind.add), + flushImmediately: false, + ); + await notifier.enqueue( + elementOp(opId: 'delete', kind: StrategyOpKind.delete), + flushImmediately: false, + ); + + expect(container!.read(strategyOpQueueProvider).pending, isEmpty); + expect(store.values, isEmpty); + }); + + test('restoring after a queued delete creates new immutable work', + () async { + final notifier = start(); + await notifier.enqueue( + elementOp(opId: 'delete', kind: StrategyOpKind.delete), + flushImmediately: false, + ); + await notifier.enqueue( + elementOp(opId: 'restore', kind: StrategyOpKind.add, value: 'restored'), + flushImmediately: false, + ); + + final pending = container!.read(strategyOpQueueProvider).pending.single; + expect(pending.op.opId, isNot(anyOf('delete', 'restore'))); + expect(pending.op.kind, StrategyOpKind.add); + expect(pending.op.payload, {'value': 'restored'}); + }); + + test('lost response then new work cannot replay the applied op ID', + () async { + await store.put( + record(status: DurableOutboxStatus.inFlight, opId: 'applied-a'), + ); + final notifier = start(); + + await notifier.enqueue( + elementOp(opId: 'work-b', value: 'b'), + flushImmediately: false, + ); + + final pending = container!.read(strategyOpQueueProvider).pending.single; + expect(pending.op.opId, isNot(anyOf('applied-a', 'work-b'))); + expect(pending.op.payload, {'value': 'b'}); + final durable = DurableOutboxRecord.fromJson( + Map.from(store.values.values.single as Map), + ); + expect(durable.pending.op.opId, pending.op.opId); + expect(durable.pending.op.payload, {'value': 'b'}); }); }); @@ -404,14 +537,12 @@ void main() { container .read(cloudCollabModeProvider.notifier) .setForceLocalFallback(true); - final enqueue = notifier.enqueue(const StrategyOp( + final enqueue = notifier.enqueue(const ElementPatchOp( opId: 'op-1', - kind: StrategyOpKind.patch, - entityType: StrategyOpEntityType.element, - entityPublicId: 'element-1', + elementPublicId: 'element-1', pagePublicId: 'page-1', payload: {'value': 'safe'}, - expectedRevision: 1, + expectedElementRevision: 1, )); await Future.delayed(Duration.zero); expect(container.read(strategyOpQueueProvider).pending, isEmpty); @@ -419,6 +550,47 @@ void main() { await enqueue; expect(container.read(strategyOpQueueProvider).pending, hasLength(1)); }); + + test('replacement stays hidden until the durable record is written', + () async { + final store = _BlockingReplacementStore(); + final container = ProviderContainer(overrides: [ + durableStrategyOutboxStoreProvider.overrideWithValue(store), + ]); + addTearDown(container.dispose); + final notifier = container.read(strategyOpQueueProvider.notifier) + ..setActiveStrategy('strategy-1', accountId: 'account-a'); + container + .read(cloudCollabModeProvider.notifier) + .setForceLocalFallback(true); + const first = ElementPatchOp( + opId: 'first', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'a'}, + expectedElementRevision: 1, + ); + const desired = ElementPatchOp( + opId: 'desired', + elementPublicId: 'element-1', + pagePublicId: 'page-1', + payload: {'value': 'b'}, + expectedElementRevision: 1, + ); + await notifier.enqueue(first, flushImmediately: false); + + final replacement = notifier.enqueue(desired, flushImmediately: false); + await Future.delayed(Duration.zero); + final beforeWrite = container.read(strategyOpQueueProvider).pending.single; + expect(beforeWrite.op.opId, 'first'); + expect(beforeWrite.op.payload, {'value': 'a'}); + + store.allowReplacement.complete(); + await replacement; + final afterWrite = container.read(strategyOpQueueProvider).pending.single; + expect(afterWrite.op.opId, isNot(anyOf('first', 'desired'))); + expect(afterWrite.op.payload, {'value': 'b'}); + }); } class _BlockingStore extends MemoryDurableStrategyOutboxStore { @@ -430,3 +602,17 @@ class _BlockingStore extends MemoryDurableStrategyOutboxStore { await super.put(record); } } + +class _BlockingReplacementStore extends MemoryDurableStrategyOutboxStore { + final allowReplacement = Completer(); + var _writes = 0; + + @override + Future put(DurableOutboxRecord record) async { + _writes += 1; + if (_writes == 2) { + await allowReplacement.future; + } + await super.put(record); + } +} diff --git a/test/strategy_page_session_provider_test.dart b/test/strategy_page_session_provider_test.dart index 66fc16d2..1e8fc5f4 100644 --- a/test/strategy_page_session_provider_test.dart +++ b/test/strategy_page_session_provider_test.dart @@ -21,6 +21,7 @@ import 'package:icarus/providers/collab/strategy_op_queue_provider.dart'; import 'package:icarus/providers/strategy_page.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/providers/strategy_settings_provider.dart'; import 'package:icarus/providers/text_draft_provider.dart'; import 'package:icarus/providers/text_provider.dart'; @@ -152,11 +153,10 @@ class _FakeStrategyOpQueueNotifier extends StrategyOpQueueNotifier { void reject(StrategyOp op) { final key = EntitySyncKey.forStrategyOp(op)!; final pending = PendingOp(op: op, clientId: 'test-client'); - final ack = OpAck( + final ack = RejectedOpAck( opId: op.opId, - status: 'reject', - reason: 'revision_mismatch', - latestRevision: 2, + rejectionReason: OpRejectionReason.revisionMismatch, + current: const ElementCurrentSnapshot(revision: 2, value: {}), ); state = state.copyWith( queuedByEntityKey: const {}, @@ -265,7 +265,7 @@ RemoteLineup _lineup(String pageId, String id) { 'ability': { 'id': 'ability-$id', 'isDeleted': false, - 'data': {'type': 'sova', 'index': 2}, + 'data': {'type': 'sova', 'index': 2.0}, 'position': {'dx': 30, 'dy': 40}, 'isAlly': true, 'rotation': 0, @@ -338,6 +338,19 @@ Future _cloudContainer({ return container; } +Future _syncContainer({ + required _FakeRemoteEditorNotifier remote, + required _FakeStrategyOpQueueNotifier queue, +}) async { + final container = ProviderContainer(overrides: [ + remoteEditorSnapshotProvider.overrideWith(() => remote), + strategyOpQueueProvider.overrideWith(() => queue), + ]); + addTearDown(container.dispose); + await container.read(remoteEditorSnapshotProvider.future); + return container; +} + Future> _openStrategyBox() async { final temp = await Directory.systemTemp.createTemp('icarus-page-session-'); Hive.init(temp.path); @@ -376,7 +389,7 @@ void main() { final op = container.read(strategyOpQueueProvider).pending.single.op; expect(op.entityType, StrategyOpEntityType.strategy); expect(op.expectedRevision, 17); - expect(op.toConvexJson()['expectedRevision'], 17); + expect(op.toConvexJson()['expectedStrategyRevision'], 17); expect( op.payload, containsPair('mapData', Maps.mapNames[MapValue.ascent]), @@ -524,7 +537,7 @@ void main() { pages: [page], activePage: _pageSnapshot(page, elements: [element]), )); - final container = await _cloudContainer( + final container = await _syncContainer( remote: remote, queue: _FakeStrategyOpQueueNotifier(), ); @@ -532,7 +545,10 @@ void main() { PlacedText(id: element.publicId, position: const Offset(10, 20)) ..text = 'restored locally', ]); - container.read(strategyProvider.notifier).consumeScheduledCloudPageSync(); + container.read(activePageLiveSyncProvider.notifier).markPageHydrated( + strategyPublicId: 'cloud-strategy', + pageId: page.publicId, + ); final desired = container.read(activePageLiveSyncProvider.notifier).syncLocalPage( @@ -544,7 +560,7 @@ void main() { expect(op, isNotNull); expect(op!.kind, StrategyOpKind.add); expect(op.expectedRevision, 4); - expect(op.toConvexJson()['expectedRevision'], 4); + expect(op.toConvexJson()['expectedElementRevision'], 4); }); test('active page update rehydrates without a strategy revision change', @@ -621,6 +637,10 @@ void main() { expect(session.transitionState, PageTransitionState.idle); expect(container.read(transitionProvider).active, isFalse); expect(remote.selectedPageIds, [pageTwo.publicId, pageOne.publicId]); + expect( + container.read(activePageLiveSyncProvider).hydratedPageId, + pageOne.publicId, + ); }); test('remote hydration waits until an unchanged text draft is dismissed', @@ -793,7 +813,7 @@ void main() { pages: [pageOne, pageTwo], activePage: _pageSnapshot(pageTwo, text: 'remote-two'), )); - final container = await _cloudContainer( + final container = await _syncContainer( remote: remote, queue: _FakeStrategyOpQueueNotifier(), ); @@ -848,6 +868,73 @@ void main() { ); }); + test('unhydrated canvas cannot author a remote lineup deletion', () async { + final page = _page('page-1', 0); + final lineup = _lineup(page.publicId, 'lineup-1'); + final container = await _syncContainer( + remote: _FakeRemoteEditorNotifier(_editorSnapshot( + pages: [page], + activePage: _pageSnapshot(page, lineups: [lineup]), + )), + queue: _FakeStrategyOpQueueNotifier(), + ); + container.read(activePageLiveSyncProvider.notifier).setContext( + strategyPublicId: 'cloud-strategy', + activePageId: page.publicId, + ); + + final desired = + container.read(activePageLiveSyncProvider.notifier).syncLocalPage( + strategyPublicId: 'cloud-strategy', + pageId: page.publicId, + ); + + expect(desired, isNull); + }); + + test('new remote lineup is not inferred as a local deletion', () async { + final page = _page('page-1', 0); + final before = _editorSnapshot( + pages: [page], + activePage: _pageSnapshot(page, text: 'remote'), + ); + final remote = _FakeRemoteEditorNotifier(before); + final container = await _cloudContainer( + remote: remote, + queue: _FakeStrategyOpQueueNotifier(), + ); + await container + .read(strategyPageSessionProvider.notifier) + .initializeForStrategy( + strategyId: 'cloud-strategy', + source: StrategySource.cloud, + selectFirstPageIfNeeded: true, + ); + + container.read(strategySaveStateProvider.notifier).markDirty(); + remote.setSnapshot(_editorSnapshot( + pages: [page], + activePage: _pageSnapshot( + page, + text: 'remote', + contentRevision: 2, + lineups: [_lineup(page.publicId, 'lineup-1')], + ), + )); + + final desired = + container.read(activePageLiveSyncProvider.notifier).syncLocalPage( + strategyPublicId: 'cloud-strategy', + pageId: page.publicId, + ); + + expect(desired, isNotNull); + expect( + desired![EntitySyncKey.lineup(page.publicId, 'lineup-1')], + isNull, + ); + }); + test('page switch persists old intent and never waits indefinitely', () async { final pageOne = _page('page-1', 0); diff --git a/test/widgets/account_avatar_test.dart b/test/widgets/account_avatar_test.dart new file mode 100644 index 00000000..370d8953 --- /dev/null +++ b/test/widgets/account_avatar_test.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/widgets/account_avatar.dart'; + +void main() { + testWidgets('falls back without reporting a UI error when avatar load fails', + (tester) async { + await tester.pumpWidget( + const MaterialApp( + home: AccountAvatar( + radius: 16, + backgroundColor: Colors.black, + avatarUrl: 'https://invalid.invalid/avatar.png', + fallback: Text('D'), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('D'), findsOneWidget); + expect(tester.takeException(), isNull); + }); +} diff --git a/test/widgets/cloud_beta_automation_semantics_test.dart b/test/widgets/cloud_beta_automation_semantics_test.dart index 6b620baa..74276ec4 100644 --- a/test/widgets/cloud_beta_automation_semantics_test.dart +++ b/test/widgets/cloud_beta_automation_semantics_test.dart @@ -1,13 +1,57 @@ +import 'dart:ui' as ui; + import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:icarus/providers/auth_provider.dart'; +import 'package:icarus/widgets/custom_text_field.dart'; import 'package:icarus/widgets/dialogs/auth/auth_dialog.dart'; import 'package:icarus/widgets/folder_navigator.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; void main() { + testWidgets('shared text fields expose live editable semantics', + (tester) async { + final semanticsHandle = tester.ensureSemantics(); + final controller = TextEditingController(text: 'child-final'); + addTearDown(controller.dispose); + + await tester.pumpWidget( + _testApp( + CustomTextField( + key: const ValueKey('shared-editable-field'), + controller: controller, + hintText: 'Folder Name', + ), + ), + ); + + final node = tester.getSemantics( + find.byKey(const ValueKey('shared-editable-field')), + ); + expect(node.flagsCollection.isEnabled, ui.Tristate.isTrue); + expect(node.getSemanticsData().hasAction(SemanticsAction.setText), isTrue); + expect(node.label, 'Folder Name'); + expect(node.value, 'child-final'); + + tester.semantics.performAction( + find.semantics.byLabel('Folder Name'), + SemanticsAction.setText, + args: 'child-renamed-web', + ); + await tester.pump(); + + expect(controller.text, 'child-renamed-web'); + expect( + tester + .getSemantics(find.byKey(const ValueKey('shared-editable-field'))) + .value, + 'child-renamed-web', + ); + semanticsHandle.dispose(); + }); + testWidgets('auth dialog exposes stable fields and actions', (tester) async { final semanticsHandle = tester.ensureSemantics(); tester.view.physicalSize = const Size(1280, 800); @@ -26,8 +70,24 @@ void main() { expect(find.byKey(const ValueKey('auth-submit-button')), findsOneWidget); expect(_textFieldNodes(tester), hasLength(2)); expect( - tester.getSemantics(find.byKey(const ValueKey('auth-email-field'))).label, - 'Email', + _textFieldNodes(tester).map((node) => node.flagsCollection.isEnabled), + everyElement(ui.Tristate.isTrue), + ); + expect( + _textFieldNodes(tester).map( + (node) => node.getSemanticsData().hasAction(SemanticsAction.setText), + ), + everyElement(isTrue), + ); + tester.semantics.performAction( + find.semantics.byLabel('Email'), + SemanticsAction.setText, + args: 'coach@example.com', + ); + await tester.pump(); + expect( + tester.getSemantics(find.byKey(const ValueKey('auth-email-field'))).value, + 'coach@example.com', ); await tester.tap(find.byKey(const ValueKey('auth-mode-switch'))); diff --git a/test/widgets/cloud_library_empty_states_test.dart b/test/widgets/cloud_library_empty_states_test.dart index 362dd4de..7e857f6a 100644 --- a/test/widgets/cloud_library_empty_states_test.dart +++ b/test/widgets/cloud_library_empty_states_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:icarus/collab/collab_models.dart'; +import 'package:icarus/collab/cloud_library_models.dart'; import 'package:icarus/const/coordinate_system.dart'; import 'package:icarus/providers/auth_provider.dart'; import 'package:icarus/providers/collab/remote_library_provider.dart'; @@ -72,10 +72,10 @@ Widget _cloudApp( () => _CloudSectionNotifier(section), ), cloudFoldersProvider.overrideWith( - (_) => Stream.value(const []), + (_) => Stream.value(const []), ), cloudStrategiesProvider.overrideWith( - (_) => Stream.value(const []), + (_) => Stream.value(const []), ), ], child: ShadApp(home: Scaffold(body: child)), diff --git a/test/widgets/confirm_alert_dialog_semantics_test.dart b/test/widgets/confirm_alert_dialog_semantics_test.dart new file mode 100644 index 00000000..99060a50 --- /dev/null +++ b/test/widgets/confirm_alert_dialog_semantics_test.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/widgets/dialogs/confirm_alert_dialog.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +void main() { + testWidgets('confirmation actions expose stable tappable semantics', + (tester) async { + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + const ShadApp( + home: Scaffold( + body: ConfirmAlertDialog( + title: 'Sign out?', + content: 'Cloud work stays online.', + confirmText: 'Sign Out', + ), + ), + ), + ); + + expect( + tester.getSemantics(find.byKey(const ValueKey('confirm-alert-cancel'))), + matchesSemantics(label: 'Cancel', isButton: true, hasTapAction: true), + ); + expect( + tester.getSemantics(find.byKey(const ValueKey('confirm-alert-confirm'))), + matchesSemantics(label: 'Sign Out', isButton: true, hasTapAction: true), + ); + semantics.dispose(); + }); +} diff --git a/test/widgets/strategy_tile_actions_semantics_test.dart b/test/widgets/strategy_tile_actions_semantics_test.dart new file mode 100644 index 00000000..0119ca8a --- /dev/null +++ b/test/widgets/strategy_tile_actions_semantics_test.dart @@ -0,0 +1,70 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus/widgets/strategy_tile/strategy_tile.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; + +void main() { + testWidgets('strategy actions expose a named tap action', (tester) async { + var pressed = false; + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + ShadApp( + home: Scaffold( + body: StrategyTileActionsButton( + strategyName: 'A Split', + onPressed: () => pressed = true, + ), + ), + ), + ); + + final finder = find.bySemanticsLabel('More actions for A Split'); + expect( + tester.getSemantics(finder), + matchesSemantics( + label: 'More actions for A Split', + isButton: true, + hasTapAction: true, + ), + ); + tester.semantics.tap(find.semantics.byLabel('More actions for A Split')); + await tester.pump(); + expect(pressed, isTrue); + semantics.dispose(); + }); + + testWidgets('strategy menu actions expose named tap actions', (tester) async { + var pressed = false; + final semantics = tester.ensureSemantics(); + + await tester.pumpWidget( + ShadApp( + home: Scaffold( + body: StrategyTileMenuActionSemantics( + label: 'Export A Split', + enabled: true, + onPressed: () => pressed = true, + child: const Text('Export'), + ), + ), + ), + ); + + final finder = find.bySemanticsLabel('Export A Split'); + expect( + tester.getSemantics(finder), + matchesSemantics( + label: 'Export A Split', + isButton: true, + isEnabled: true, + hasEnabledState: true, + hasTapAction: true, + ), + ); + tester.semantics.tap(find.semantics.byLabel('Export A Split')); + await tester.pump(); + expect(pressed, isTrue); + semantics.dispose(); + }); +} diff --git a/third_party/convex_flutter/ARCHITECTURE.md b/third_party/convex_flutter/ARCHITECTURE.md new file mode 100644 index 00000000..b66d6013 --- /dev/null +++ b/third_party/convex_flutter/ARCHITECTURE.md @@ -0,0 +1,784 @@ +# Architecture & Platform Support - convex_flutter + +## v3.0.0 Major Update: Web Platform Support 🌐 + +**convex_flutter** now supports **ALL Flutter platforms** including web! The package intelligently uses different implementations based on the target platform: + +- **Web**: Pure Dart implementation (no Rust required) +- **Native** (Android, iOS, macOS, Windows, Linux): FFI + Rust SDK + +## Table of Contents +- [Platform Architecture Overview](#platform-architecture-overview) +- [Web Platform Implementation (NEW in v3.0.0)](#web-platform-implementation-new-in-v300) +- [Native Platform Implementation](#native-platform-implementation) +- [Why Rust is Required (Native Only)](#why-rust-is-required-native-only) +- [Who Needs Rust Installed](#who-needs-rust-installed) +- [How the Package Works](#how-the-package-works) +- [Can Rust Dependency Be Removed?](#can-rust-dependency-be-removed) +- [Alternatives & Tradeoffs](#alternatives--tradeoffs) +- [Impact on Developers](#impact-on-developers) +- [Future Possibilities](#future-possibilities) + +--- + +## Platform Architecture Overview + +### Multi-Platform Implementation Strategy + +The package uses **conditional imports** to select the appropriate implementation at compile time: + +```dart +// lib/src/convex_client.dart +import 'impl/convex_client_native.dart' // FFI + Rust + if (dart.library.js_interop) 'impl/convex_client_web.dart'; // Pure Dart +``` + +### Architecture Comparison + +| Platform | Implementation | Rust Required | WebSocket Source | +|----------|---------------|---------------|------------------| +| **Web** | Pure Dart | ❌ No | Browser WebSocket API | +| **Android** | FFI + Rust | ✅ Yes (build-time) | Convex Rust SDK | +| **iOS** | FFI + Rust | ✅ Yes (build-time) | Convex Rust SDK | +| **macOS** | FFI + Rust | ✅ Yes (build-time) | Convex Rust SDK | +| **Windows** | FFI + Rust | ✅ Yes (build-time) | Convex Rust SDK | +| **Linux** | FFI + Rust | ✅ Yes (build-time) | Convex Rust SDK | + +--- + +## Web Platform Implementation (NEW in v3.0.0) + +### Why Web Needed Different Approach + +**Problem**: FFI (Foreign Function Interface) doesn't work on web platform +- Web runs in browser JavaScript sandbox +- Cannot execute native compiled code +- `dart:ffi` is not available on web + +**Solution**: Implement Convex WebSocket protocol in pure Dart + +### Web Architecture + +``` +┌─────────────────────────────────────┐ +│ Dart Layer (Flutter Web App) │ ← Your app code +│ - ConvexClient API (same as native)│ +│ - Streams, Futures │ +│ - Flutter-friendly interfaces │ +└──────────────┬──────────────────────┘ + │ Direct Dart calls (no FFI) +┌──────────────▼──────────────────────┐ +│ WebConvexClient (Pure Dart) │ ← Pure Dart implementation +│ - WebSocket management │ +│ - Convex wire protocol │ +│ - State management │ +│ - Subscription handling │ +└──────────────┬──────────────────────┘ + │ package:web WebSocket API +┌──────────────▼──────────────────────┐ +│ Browser WebSocket │ ← Browser native API +│ - Real-time WebSocket client │ +│ - Managed by browser │ +│ - No compilation required │ +└─────────────────────────────────────┘ +``` + +### Web Implementation Details + +**File**: `lib/src/impl/convex_client_web.dart` (~800 lines of pure Dart) + +**Key Features Implemented**: +- ✅ RFC 4122 compliant UUID v4 generation for session IDs +- ✅ Convex WebSocket wire protocol implementation: + - Connect message with session management + - ModifyQuerySet with version tracking + - Mutation/Action/Query execution + - Transition messages for real-time updates + - Ping/Pong heartbeat +- ✅ Real-time subscriptions with automatic cleanup +- ✅ Connection state monitoring +- ✅ Automatic reconnection with exponential backoff +- ✅ Authentication token management +- ✅ Error handling and timeout management + +**Dependencies (Web Only)**: +```yaml +dependencies: + web: ^1.0.0 # Browser WebSocket API access + http: ^1.2.0 # HTTP client for REST fallback (future) +``` + +**Protocol Messages**: +```dart +// Connect +{ + "type": "Connect", + "sessionId": "550e8400-e29b-41d4-a716-446655440000", // RFC 4122 UUID + "maxObservedTimestamp": null, + "connectionCount": 1, + "clientTs": 1704931200000, + "lastCloseReason": null +} + +// ModifyQuerySet (subscribe) +{ + "type": "ModifyQuerySet", + "baseVersion": 0, + "newVersion": 1, + "modifications": [{ + "type": "Add", + "queryId": 1, + "udfPath": "messages:list", + "args": [{}] + }] +} + +// Mutation +{ + "type": "Mutation", + "requestId": 1, // u32 integer + "udfPath": "messages:send", + "args": [{"body": "Hello"}] +} +``` + +### Web vs Native API Parity + +**100% API Compatibility**: Same public API works on both platforms + +```dart +// This exact code works identically on web AND native! +final client = ConvexClient.instance; + +// Queries +final result = await client.query('users:list', {}); + +// Mutations +await client.mutation(name: 'messages:send', args: {'body': 'Hi'}); + +// Subscriptions +final sub = await client.subscribe( + name: 'messages:list', + args: {}, + onUpdate: (data) => print(data), + onError: (msg, data) => print(msg), +); + +// Connection state +client.connectionState.listen((state) => print(state)); + +// Authentication +await client.setAuth(token: 'jwt-token'); +``` + +--- + +## Native Platform Implementation + +### TL;DR +**Native platforms use FFI (Foreign Function Interface) to wrap the official Convex Rust SDK.** This means the core Convex client logic is written in Rust and compiled to native code, with Dart code calling into it via FFI. + +### Technical Explanation + +The package architecture involves three layers: + +``` +┌─────────────────────────────────────┐ +│ Dart Layer (Flutter App) │ ← Your app code +│ - ConvexClient API │ +│ - Streams, Futures │ +│ - Flutter-friendly interfaces │ +└──────────────┬──────────────────────┘ + │ FFI Bridge (flutter_rust_bridge) +┌──────────────▼──────────────────────┐ +│ Rust Layer (Native Code) │ ← Compiled Rust +│ - MobileConvexClient │ +│ - WebSocket management │ +│ - State management │ +└──────────────┬──────────────────────┘ + │ Rust library dependency +┌──────────────▼──────────────────────┐ +│ Convex Rust SDK (convex crate) │ ← Official Convex SDK +│ - Real-time WebSocket client │ +│ - Query/Mutation/Action execution │ +│ - Connection management │ +└─────────────────────────────────────┘ +``` + +### Key Dependencies + +**In `pubspec.yaml`:** +```yaml +dependencies: + flutter_rust_bridge: ^2.11.1 # Dart ↔ Rust FFI bridge + ffi: ^2.1.3 # Dart FFI support +``` + +**In `rust/Cargo.toml`:** +```toml +[dependencies] +convex = { version = "0.9" } # Official Convex Rust SDK +flutter_rust_bridge = "=2.11.1" # Bridge codegen +tokio = { version = "1", features = ["full"] } # Async runtime +``` + +**Plugin Configuration in `pubspec.yaml`:** +```yaml +flutter: + plugin: + platforms: + android: + ffiPlugin: true # ← This marks it as FFI plugin + ios: + ffiPlugin: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + windows: + ffiPlugin: true +``` + +### Why Use Rust Instead of Pure Dart? + +1. **Official SDK**: Convex provides an official Rust SDK with full WebSocket support +2. **Performance**: Native code (compiled Rust) is faster than interpreted Dart for intensive operations +3. **Code Reuse**: Leverage the battle-tested Convex Rust client instead of reimplementing from scratch +4. **Real-time Features**: WebSocket management, connection pooling, and async I/O are built-in +5. **Type Safety**: Rust's strong type system catches errors at compile time +6. **Cross-platform**: Rust compiles to all Flutter platforms (Android, iOS, Windows, macOS, Linux) + +--- + +## Who Needs Rust Installed + +### 1. Package Developers (Maintainers) ✅ NEED RUST + +**Who**: Anyone modifying the convex_flutter package itself + +**Why**: To build and test Rust code changes + +**Requirements**: +```bash +# Install Rust toolchain +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Verify installation +rustc --version +cargo --version + +# Platform-specific tools +# Android: NDK (via Android SDK Manager) +# iOS/macOS: Xcode Command Line Tools +# Windows: Visual Studio Build Tools (C++) +# Linux: build-essential, clang, pkg-config +``` + +### 2. App Developers (Package Users) ⚠️ CURRENTLY NEED RUST + +**Who**: Anyone building a Flutter app that depends on `convex_flutter` + +**Why**: Flutter's build system compiles the Rust code when building your app + +**The Problem**: This is a significant barrier to adoption. Most Flutter developers don't have Rust installed and shouldn't need to. + +**What Happens When Building**: +```bash +# When you run: +flutter build apk + +# Flutter build system: +1. Detects FFI plugin (ffiPlugin: true) +2. Looks for Rust source in rust/ directory +3. Invokes `cargo build --release` for target platform +4. Compiles Rust code to native library (.so, .dylib, .dll) +5. Bundles native library into app package +6. ❌ FAILS if Rust toolchain not installed +``` + +**Error Without Rust**: +``` +Error: Unable to find cargo in PATH. Rust toolchain is required. +Please install Rust from https://rustup.rs +``` + +### 3. End Users (App Users) ✅ DON'T NEED RUST + +**Who**: People downloading your app from App Store/Play Store + +**Why**: The compiled native libraries are bundled in the app package + +**What They Get**: Pre-compiled native code (no Rust needed) + +--- + +## How the Package Works + +### Build Process + +```mermaid +graph TD + A[flutter build] --> B{Detect FFI Plugin} + B --> C[Invoke cargo build] + C --> D[Compile Rust → Native Library] + D --> E[Bundle .so/.dylib/.dll into app] + E --> F[App can call Rust via FFI] +``` + +### Runtime Flow + +**Example: Executing a Query** + +```dart +// 1. Dart: User calls query +final result = await ConvexClient.instance.query('users:list', {}); +``` + +↓ + +```dart +// 2. Dart: ConvexClient calls Rust via FFI bridge +final rustResult = await _mobileClient.query( + name: 'users:list', + args: '{}', + timeout: Duration(seconds: 30), +); +``` + +↓ + +```rust +// 3. Rust: MobileConvexClient receives call +pub async fn query(&self, name: String, args: String, timeout: Duration) -> Result { + let client = self.connected_client().await?; // Get Convex client + + // Parse args, execute query via Convex SDK + let result = client.query(name, args).await?; + + // Return JSON string to Dart + Ok(serde_json::to_string(&result)?) +} +``` + +↓ + +```rust +// 4. Convex Rust SDK: Execute query +// - Establish WebSocket connection +// - Send query request +// - Receive response +// - Return result +``` + +↓ + +```dart +// 5. Dart: Return result to app +return jsonDecode(rustResult); +``` + +### File Structure + +``` +convex_flutter/ +├── lib/ # Dart code (Flutter layer) +│ ├── convex_flutter.dart # Public API +│ └── src/ +│ ├── convex_client.dart # Main client (Dart) +│ ├── rust/ # Generated FFI bindings +│ │ └── lib.dart # Auto-generated by flutter_rust_bridge +│ └── *.dart # Other Dart types/utilities +│ +├── rust/ # Rust code (Native layer) +│ ├── Cargo.toml # Rust dependencies +│ ├── src/ +│ │ ├── lib.rs # Main Rust implementation (wraps Convex SDK) +│ │ └── frb_generated.rs # Auto-generated FFI bindings +│ └── target/ # Compiled Rust artifacts (1.9GB+) +│ +├── pubspec.yaml # Flutter package config (ffiPlugin: true) +└── README.md # Package documentation +``` + +**Code Statistics**: +- **Dart files**: 33 (UI, API, types) +- **Rust files**: 2 (core client logic) +- **Lines of Rust**: ~800 lines wrapping Convex SDK + +--- + +## Can Rust Dependency Be Removed? + +### Short Answer: **Technically YES, but at SIGNIFICANT cost** + +### Long Answer: Multiple Approaches, Each with Major Tradeoffs + +--- + +## Alternatives & Tradeoffs + +### Option 1: Pure Dart Implementation ❌ NOT RECOMMENDED + +**Approach**: Rewrite entire Convex client in Dart + +**Pros**: +- ✅ No Rust dependency +- ✅ Easier for Flutter developers to contribute +- ✅ No FFI bridge overhead +- ✅ Single language ecosystem + +**Cons**: +- ❌ **MASSIVE development effort** (thousands of lines of code) +- ❌ Reimplementing WebSocket protocol, connection management, state handling +- ❌ Maintaining parity with official Convex SDK features +- ❌ Testing and bug fixes (Rust SDK is battle-tested) +- ❌ Ongoing maintenance burden (keeping up with Convex API changes) +- ❌ Potential performance issues (Dart vs native code) + +**Estimated Effort**: 3-6 months of full-time development + ongoing maintenance + +**Verdict**: Only viable if Convex provides an official Dart SDK + +--- + +### Option 2: Pre-compiled Native Binaries ⚠️ POSSIBLE BUT COMPLEX + +**Approach**: Build Rust code in advance for all platforms, distribute binaries with package + +**How It Works**: +``` +1. Package maintainer builds Rust code for all targets: + - Android: arm64-v8a, armeabi-v7a, x86_64, x86 + - iOS: arm64 (device), x86_64 (simulator) + - macOS: arm64 (Apple Silicon), x86_64 (Intel) + - Windows: x86_64 + - Linux: x86_64, arm64 + +2. Include all binaries in package (in android/libs/, ios/, etc.) + +3. Flutter build system uses pre-built binaries instead of compiling +``` + +**Pros**: +- ✅ App developers don't need Rust installed +- ✅ Faster builds (no Rust compilation) +- ✅ Same functionality as current implementation + +**Cons**: +- ❌ **Large package size** (~50-100MB for all platforms/architectures) +- ❌ **CI/CD complexity** (must build for 10+ target platforms) +- ❌ **Security concerns** (distributing pre-built binaries) +- ❌ Package maintainer needs all platform build environments +- ❌ pub.dev size limits (10MB for packages, need special approval for larger) +- ❌ Still need Rust for package development + +**Package Size Impact**: +``` +Current package size: 328 KB (source only) +With pre-compiled binaries: ~80-120 MB (all platforms/architectures) + +Breakdown: +- Android (4 architectures): ~20-30 MB +- iOS (2 architectures): ~15-20 MB +- macOS (2 architectures): ~15-20 MB +- Windows: ~10-15 MB +- Linux: ~10-15 MB +``` + +**Verdict**: Solves developer experience but creates distribution challenges + +--- + +### Option 3: REST API Only (No WebSockets) ❌ LOSES KEY FEATURES + +**Approach**: Use Convex HTTP API directly (no WebSockets) + +**Pros**: +- ✅ Pure Dart implementation (easy to write) +- ✅ No Rust dependency +- ✅ Simple HTTP client (`package:http`) + +**Cons**: +- ❌ **NO real-time subscriptions** (major feature loss) +- ❌ **NO automatic reconnection** on network changes +- ❌ **NO connection state management** +- ❌ Must poll for updates (inefficient, battery drain) +- ❌ Higher latency for real-time features +- ❌ Not using official Convex SDK + +**What You Lose**: +```dart +// ❌ Real-time subscriptions (lost) +client.subscribe( + name: 'messages:list', + args: {}, + onUpdate: (messages) => print('New messages: $messages'), +); + +// ❌ Connection state monitoring (lost) +client.connectionState.listen((state) { + print('Connection: $state'); +}); + +// ❌ Automatic reconnection (lost) +// ❌ WebSocket efficiency (lost) +``` + +**Verdict**: Only viable for simple apps without real-time requirements + +--- + +### Option 4: Hybrid Approach ⚠️ BEST COMPROMISE + +**Approach**: Offer TWO packages + +1. **`convex_flutter`** (current): Full-featured FFI package with Rust +2. **`convex_flutter_lite`** (new): Pure Dart HTTP-only version + +**Pros**: +- ✅ Developers choose based on needs +- ✅ Simple apps can avoid Rust dependency +- ✅ Advanced apps get full features +- ✅ Clear upgrade path (lite → full) + +**Cons**: +- ❌ Maintain two packages +- ❌ Feature parity issues +- ❌ Documentation duplication +- ❌ Potential confusion for users + +**Verdict**: Good middle ground if demand justifies the effort + +--- + +### Option 5: Official Convex Dart SDK 🎯 IDEAL SOLUTION + +**Approach**: Ask Convex to provide an official Dart/Flutter SDK + +**Pros**: +- ✅ No Rust dependency (if written in Dart) +- ✅ Official support from Convex +- ✅ Feature parity guaranteed +- ✅ Professional maintenance + +**Cons**: +- ❌ Outside our control +- ❌ May not happen (Convex prioritizes other platforms) +- ❌ Timeline uncertain + +**Action**: Submit feature request to Convex team + +**Verdict**: Best long-term solution, but not immediately available + +--- + +## Impact on Developers + +### Current Developer Experience (With Rust) + +**First-time setup**: +```bash +# 1. Install Rust (5-10 minutes) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env + +# 2. Install platform tools +# Android: Install NDK via Android SDK Manager +# iOS/macOS: xcode-select --install +# Windows: Install Visual Studio Build Tools +# Linux: sudo apt-get install build-essential clang pkg-config + +# 3. Add package to Flutter project +flutter pub add convex_flutter + +# 4. First build (slow - compiles Rust) +flutter run # Takes 2-5 minutes on first build + +# 5. Subsequent builds (faster - uses cache) +flutter run # Takes 30-60 seconds +``` + +**Common Issues**: +- ❌ "cargo: command not found" → Rust not installed +- ❌ "NDK not found" → Android NDK missing +- ❌ Long build times (Rust compilation adds 1-3 minutes) +- ❌ Large build artifacts (rust/target/ = 1.9GB) + +**Comparison to Pure Dart Packages**: +```bash +# Pure Dart package (e.g., http, provider, riverpod) +flutter pub add http # Done in 5 seconds +flutter run # Builds in 30 seconds + +# convex_flutter (FFI plugin) +flutter pub add convex_flutter # Requires Rust setup (10 minutes) +flutter run # Builds in 3-5 minutes (first time) +``` + +--- + +## Future Possibilities + +### 1. Streamlined Rust Installation 🔧 + +**Idea**: Provide automated setup script + +```bash +# Example setup script +curl -sSf https://raw.githubusercontent.com/jkuldev/convex_flutter/main/setup.sh | sh + +# Script would: +# 1. Detect OS (macOS, Linux, Windows) +# 2. Install Rust if missing +# 3. Install platform tools (NDK, Xcode, etc.) +# 4. Configure environment +# 5. Run test build +``` + +**Impact**: Reduces setup friction from 30 minutes to 5 minutes + +--- + +### 2. Pre-compiled Binaries via GitHub Releases 📦 + +**Idea**: Host pre-compiled binaries separately, download on demand + +```yaml +# pubspec.yaml +dependencies: + convex_flutter: ^2.2.0 + +# On first build, Flutter plugin downloads pre-built binaries +# from GitHub releases instead of compiling Rust +``` + +**How**: +1. CI/CD builds binaries for all platforms +2. Binaries uploaded to GitHub Releases +3. Flutter plugin downloads correct binary for target platform +4. Falls back to Rust compilation if download fails + +**Impact**: +- ✅ App developers don't need Rust +- ✅ Faster builds +- ❌ Still large downloads (~10-20MB per platform) + +--- + +### 3. Official Convex Dart SDK 🎯 + +**Ideal Long-term Solution**: Convex provides official Dart/Flutter SDK + +**Request to Convex**: +``` +Subject: Feature Request - Official Dart/Flutter SDK + +Dear Convex Team, + +We maintain convex_flutter, a community package wrapping your Rust SDK. +Current architecture requires all Flutter developers to install Rust, +which is a significant adoption barrier. + +Would Convex consider providing an official Dart/Flutter SDK? + +Benefits: +- Wider Flutter ecosystem adoption +- Better developer experience +- Official support and maintenance +- Feature parity with other platforms (JS, Python, Rust) + +Thank you for consideration. +``` + +--- + +### 4. WebAssembly (WASM) Compilation 🌐 + +**Future Tech**: Compile Rust to WASM, run in Dart VM + +**Status**: Experimental (Flutter WASM support is evolving) + +**Potential**: +- ✅ No Rust toolchain needed +- ✅ Smaller package size +- ✅ Same Rust code +- ❌ Performance overhead (WASM vs native) +- ❌ Flutter WASM support still maturing + +--- + +## Summary & Recommendations + +### Current State +- ✅ **Fully functional** with real-time WebSocket support +- ✅ **Production-ready** (v2.2.0) +- ❌ **Requires Rust** for all developers (barrier to adoption) + +### Short-term Recommendations + +**For Package Maintainers**: +1. **Document Rust requirement clearly** in README (add prominent warning) +2. **Provide setup guide** with troubleshooting +3. **Add FAQ section** explaining why Rust is needed +4. **Consider pre-compiled binaries** for popular platforms (Android/iOS first) + +**For App Developers**: +1. **Accept Rust requirement** if you need real-time features +2. **Use alternative packages** if Rust is a dealbreaker (e.g., HTTP-only Convex client) +3. **Submit feedback** about Rust requirement (helps prioritize solutions) + +### Long-term Recommendations + +1. **Investigate pre-compiled binaries** (GitHub Actions CI/CD) +2. **Create `convex_flutter_lite`** (pure Dart HTTP-only version) +3. **Request official Dart SDK** from Convex team +4. **Monitor Flutter WASM** progress + +### Decision Matrix + +| Feature | Current (Rust FFI) | Pre-compiled | Pure Dart | HTTP-only | +|---------|-------------------|--------------|-----------|-----------| +| Real-time subscriptions | ✅ | ✅ | ✅ | ❌ | +| Connection state | ✅ | ✅ | ✅ | ❌ | +| No Rust needed | ❌ | ✅ | ✅ | ✅ | +| Small package size | ✅ | ❌ | ✅ | ✅ | +| Easy maintenance | ✅ | ⚠️ | ❌ | ✅ | +| Performance | ✅ | ✅ | ⚠️ | ⚠️ | +| Official SDK parity | ✅ | ✅ | ❌ | ❌ | + +--- + +## FAQ + +### Q: Why not just use HTTP requests instead of WebSockets? +**A**: WebSockets provide real-time bidirectional communication. With HTTP, you'd need to poll for updates, which is inefficient, drains battery, and has higher latency. Convex's real-time subscriptions require WebSockets. + +### Q: Can I use this package without installing Rust? +**A**: ✅ YES for web platform! No Rust required when building for web. For native platforms (Android, iOS, macOS, Windows, Linux), Rust is still required at build-time. If your app only targets web, you can skip Rust installation entirely. + +### Q: Will this work on web platform? +**A**: ✅ YES! As of v3.0.0, web platform is fully supported with a pure Dart implementation. No Rust required for web builds. See [Web Platform Implementation](#web-platform-implementation-new-in-v300) section above. + +### Q: How much does Rust compilation add to build time? +**A**: First build: 2-5 minutes. Subsequent builds: 30-60 seconds (cached). Release builds take longer (5-10 minutes). + +### Q: Can I distribute my app without users needing Rust? +**A**: Yes! End users don't need Rust. The compiled native libraries are bundled in your app package. Only developers building the app need Rust. + +### Q: Is there a roadmap for removing Rust dependency? +**A**: We're investigating pre-compiled binaries for v3.0. Long-term, we hope Convex provides an official Dart SDK. See [Future Possibilities](#future-possibilities) section. + +--- + +## Contributing + +If you have ideas for reducing Rust dependency burden: +1. Open an issue: https://github.com/jkuldev/convex_flutter/issues +2. Discuss in PR: https://github.com/jkuldev/convex_flutter/pulls +3. Contact maintainers: https://jkuldev.com + +--- + +**Document Version**: 2.0 +**Last Updated**: 2026-01-10 +**Package Version**: 3.0.0 diff --git a/third_party/convex_flutter/CHANGELOG.md b/third_party/convex_flutter/CHANGELOG.md new file mode 100644 index 00000000..75dc3b2b --- /dev/null +++ b/third_party/convex_flutter/CHANGELOG.md @@ -0,0 +1,234 @@ +## 3.0.1 + +### Bug Fixes + +- **Fixed argument types from `Map` to `Map`** across all operations (query, mutation, action, subscribe) + - Nested objects (e.g., `paginationOpts`), arrays, numbers, and booleans are now properly supported as argument values + - Fix applied consistently across public API, interface, native, and web implementations + - Removed `toString()` conversion in mutation and action that silently destroyed nested argument structures + - Closes #15 + +## 3.0.0 + +### Major New Features + +- **🌐 Web Platform Support**: Full web platform support with pure Dart implementation + - Uses native browser WebSocket API (no FFI required) + - 100% API compatibility with native platforms + - Automatic platform selection via conditional imports + - No Rust toolchain required for web builds + - All features work identically on web: queries, mutations, actions, subscriptions, auth + +### Web Implementation Details + +- Implemented Convex WebSocket wire protocol in pure Dart: + - RFC 4122 compliant UUID v4 generation for session IDs + - Proper protocol message formatting (Connect, ModifyQuerySet, Mutation, Action, Transition, Ping/Pong) + - Query set version tracking with baseVersion/newVersion + - Integer requestId (u32) for protocol compliance + - Real-time subscription management with automatic cleanup + - Connection state monitoring and automatic reconnection + - Ping/Pong heartbeat for connection keepalive + +### Critical Bug Fixes + +- **Fixed macOS native platform connection issues**: + - Root cause: Missing network entitlements in App Sandbox configuration + - Added `com.apple.security.network.client` to both DebugProfile.entitlements and Release.entitlements + - macOS apps can now establish WebSocket connections to Convex backend + +- **Fixed Android missing INTERNET permission**: + - Added `` to AndroidManifest.xml + - Android apps now have proper network access + +- **Fixed Rust rustls CryptoProvider error**: + - Removed `default-features = false` from convex dependency in Cargo.toml + - rustls 0.23+ now has proper CryptoProvider configuration + +### Improvements + +- **Platform Configuration Documentation**: + - New PLATFORM_CONFIGURATION.md guide with setup instructions for all platforms + - Updated README.md with platform-specific requirements + - Clear troubleshooting guides for common connection issues + +- **Example App**: + - All platforms (web, iOS, Android, macOS) now properly configured + - Works on web without Rust toolchain + - Demonstrates cross-platform compatibility + +- **Rust SDK Update**: + - Upgraded convex SDK from 0.9.0 to 0.10.2 + - Better protocol compatibility with Convex backend + +### Platform Support Matrix + +| Platform | Status | Implementation | Network Config Required | +|----------|--------|----------------|-------------------------| +| Web | ✅ New | Pure Dart | None | +| iOS | ✅ Working | FFI + Rust | None | +| macOS | ✅ Fixed | FFI + Rust | Network entitlements | +| Android | ✅ Fixed | FFI + Rust | INTERNET permission | +| Windows | ✅ Working | FFI + Rust | None | +| Linux | ✅ Working | FFI + Rust | None | + +### API Changes + +None - 100% backward compatible. The same API works across all platforms. + +### Breaking Changes + +None - this is a feature release with bug fixes, no breaking changes to existing API. + +### New Files + +- `lib/src/impl/convex_client_web.dart` - Pure Dart WebSocket implementation for web +- `lib/src/impl/convex_client_native.dart` - FFI implementation for native platforms (refactored) +- `PLATFORM_CONFIGURATION.md` - Comprehensive platform setup guide +- `WEB_SUCCESS.md` - Web implementation verification documentation +- `NATIVE_PLATFORM_FIX.md` - Native platform fixes documentation + +### Modified Files + +- `example/macos/Runner/DebugProfile.entitlements` - Added network permissions +- `example/macos/Runner/Release.entitlements` - Added network permissions +- `example/android/app/src/main/AndroidManifest.xml` - Added INTERNET permission +- `rust/Cargo.toml` - Updated convex SDK and removed default-features = false +- `lib/src/convex_client.dart` - Refactored to use platform-specific implementations +- `README.md` - Added web platform documentation and platform configuration guide + +### Migration Guide + +No migration needed - existing code works without changes on all platforms including web. + +To build for web: +```bash +flutter build web +``` + +No Rust toolchain required for web builds! + +### Known Issues + +None - all platforms tested and working. + +--- + +## 1.0.2 + +- Added support for Dart 3.7.0 +- Added support for Flutter 3.3.0 +- Added support for Flutter 3.10.0 +- Added support for Flutter 3.11.0 +- Added support for Flutter 3.12.0 +- Added support for Flutter 3.13.0 +- Added support for Flutter 3.14.0 + +## 1.0.3 + +- Updated flutter_rust_bridge package to 2.9.0 + +## 1.0.4 + +- Updated flutter_rust_bridge package to 2.10.0 + +## 1.2.0 + + - Package version updated + +## 2.0.0 + +- Replaced ArcSubscriptionHandle with SubscriptionHandle + +## 2.1.0 + +### New Features + +- **Singleton Pattern**: New `ConvexClient.initialize(ConvexConfig)` method with `ConvexClient.instance` access +- **Operation Timeouts**: Configurable timeout for all queries, mutations, and actions (default: 30 seconds) +- **Connection Management**: Manual connection checking with `checkConnection()` and `reconnect()` methods +- **Lifecycle Monitoring**: Stream of app lifecycle events (resumed, paused, inactive, detached) +- **Configuration Class**: New `ConvexConfig` class for cleaner initialization + +### Bug Fixes + +- Fixed critical Rust subscription panic when WebSocket connection closes unexpectedly +- Subscription streams now exit gracefully instead of crashing the app + +### Improvements + +- Better error handling for connection issues with `ConnectionStatus` enum +- App lifecycle integration with `AppLifecycleObserver` +- Comprehensive documentation updates with new usage examples +- Example app updated to demonstrate new features + +### API Changes + +- **Deprecated**: `ConvexClient.init()` is now deprecated, use `ConvexClient.initialize(ConvexConfig)` instead +- **New**: `ConvexClient.instance` - Access singleton anywhere +- **New**: `ConvexClient.initialize(ConvexConfig)` - Initialize with configuration +- **New**: `checkConnection()` - Manual connection status check +- **New**: `reconnect()` - Manual reconnection attempt +- **New**: `lifecycleEvents` stream - Monitor app lifecycle +- **Enhanced**: All queries, mutations, and actions now respect `operationTimeout` + +### Breaking Changes + +None - backward compatibility maintained through deprecated methods + +## 2.2.0 + +### New Features + +- **Real-Time WebSocket Connection State**: Monitor WebSocket connection status via reactive streams + - `connectionState` stream - Real-time connection state updates (Connected/Connecting) + - `currentConnectionState` getter - Synchronous access to current state + - `isConnected` getter - Quick boolean check for connection status + - Automatic state transitions when WebSocket connects/disconnects + - No polling required - pure event-driven updates + +### Bug Fixes + +- **Fixed critical race condition in WebSocket connection initialization** + - Issue: State change callback was registered after WebSocket connection began, causing state transitions to be lost + - Root cause: Async task spawning in `connected_client()` created unpredictable timing delays + - Solution: Removed task spawning and build ConvexClient directly in async context + - Result: Callback is now guaranteed to be registered before `builder.build()` is called + +- **Fixed WebSocket connection state stuck on "connecting"** + - Issue: Example app showed "connecting" forever without transitioning to "connected" + - Root cause: No operations were triggered on app startup, so `connected_client()` was never called + - Solution: Added auto-connection trigger in example app's HomeScreen initialization + - Result: Connection establishes automatically on startup with proper state transitions + +### Improvements + +- Enhanced example app with comprehensive WebSocket connection state demonstrations: + - Connection status indicator in app bar with real-time visual feedback + - Dedicated Connection screen showing current state and history + - Automatic connection on app startup + - All 5 screens demonstrating different SDK capabilities + - Added HEALTH_CHECK.md guide for setting up health check queries + +- Documentation improvements: + - Comprehensive WebSocket connection state usage examples + - Recommended health check query pattern using `health:ping` + - TypeScript example for creating health check query in Convex backend + - Updated all examples to use dedicated health check instead of `messages:list` + - Deprecated `checkConnection()` in favor of real-time `connectionState` stream + +- Code quality: + - Comprehensive debug logging for troubleshooting connection issues + - Better error handling in auto-connection flow + - Clearer comments explaining lazy initialization + +### API Changes + +- **New**: `connectionState` stream - Real-time WebSocket connection state updates (`Stream`) +- **New**: `currentConnectionState` getter - Synchronous access to current connection state +- **New**: `isConnected` getter - Boolean check for WebSocket connection status +- **Deprecated**: `checkConnection()` - Use `connectionState` stream for real-time monitoring instead + +### Breaking Changes + +None - all changes are additive and maintain backward compatibility \ No newline at end of file diff --git a/third_party/convex_flutter/CONTRIBUTING.md b/third_party/convex_flutter/CONTRIBUTING.md new file mode 100644 index 00000000..c1d9e4fd --- /dev/null +++ b/third_party/convex_flutter/CONTRIBUTING.md @@ -0,0 +1,504 @@ +# Contributing to convex_flutter + +Thank you for your interest in contributing to `convex_flutter`! This document provides guidelines and instructions for contributing to the project. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [How Can I Contribute?](#how-can-i-contribute) +- [Development Setup](#development-setup) +- [Project Structure](#project-structure) +- [Making Changes](#making-changes) +- [Testing](#testing) +- [Submitting Changes](#submitting-changes) +- [Style Guidelines](#style-guidelines) +- [Platform-Specific Contributions](#platform-specific-contributions) + +--- + +## Code of Conduct + +This project adheres to a code of conduct that all contributors are expected to follow: + +- Be respectful and inclusive +- Welcome newcomers and help them get started +- Focus on constructive criticism +- Respect differing viewpoints and experiences +- Accept responsibility and apologize for mistakes + +## How Can I Contribute? + +### Reporting Bugs + +Before creating bug reports, please check existing issues to avoid duplicates. When creating a bug report, include: + +- **Clear title** describing the issue +- **Detailed description** of the problem +- **Steps to reproduce** the behavior +- **Expected vs actual behavior** +- **Environment details**: + - Flutter version (`flutter --version`) + - Dart version + - Platform (Web, Android, iOS, macOS, Windows, Linux) + - Package version + - Rust version (for native platforms) +- **Stack traces or error messages** +- **Minimal reproducible example** if possible + +**Template**: +```markdown +**Description**: Brief description of the issue + +**Steps to Reproduce**: +1. Initialize ConvexClient with... +2. Call query/mutation/subscribe... +3. Observe error... + +**Expected**: What should happen +**Actual**: What actually happens + +**Environment**: +- Flutter: 3.19.0 +- Dart: 3.3.0 +- Platform: Web / Android / iOS / etc. +- convex_flutter: 3.0.0 +- Rust: 1.75.0 (if applicable) + +**Error Output**: +``` +[Paste error here] +``` + +**Additional Context**: Any other relevant information +``` + +### Suggesting Enhancements + +Enhancement suggestions are tracked as GitHub issues. When creating an enhancement suggestion, include: + +- **Clear title** describing the enhancement +- **Detailed description** of the proposed feature +- **Use case** explaining why this would be useful +- **Proposed implementation** (if you have ideas) +- **Alternatives considered** + +### Pull Requests + +We actively welcome pull requests! To contribute code: + +1. **Fork** the repository +2. **Create a branch** from `main` (`git checkout -b feature/my-feature`) +3. **Make your changes** following our style guidelines +4. **Test your changes** on relevant platforms +5. **Commit your changes** with clear commit messages +6. **Push to your fork** (`git push origin feature/my-feature`) +7. **Open a Pull Request** with a clear description + +--- + +## Development Setup + +### Prerequisites + +**For All Contributors**: +- Flutter SDK (>= 3.3.0) +- Dart SDK (>= 3.8.1) +- Git +- A code editor (VS Code, Android Studio, etc.) + +**For Native Platform Development**: +- Rust toolchain (`rustup` + `cargo`) +- Platform-specific tools: + - **Android**: JDK 11, Android SDK, NDK + - **iOS/macOS**: Xcode, CocoaPods + - **Windows**: Visual Studio Build Tools (C++) + - **Linux**: build-essential, clang, pkg-config + +**For Web Platform Development**: +- No Rust required! +- Just Flutter and Dart + +### Initial Setup + +```bash +# 1. Clone your fork +git clone https://github.com/YOUR_USERNAME/convex_flutter.git +cd convex_flutter + +# 2. Install Rust (skip if only working on web) +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +source $HOME/.cargo/env + +# 3. Install dependencies +flutter pub get + +# 4. Run example app +cd example +flutter pub get +flutter run -d chrome # For web +# OR +flutter run -d macos # For native +``` + +### Setting Up for Development + +```bash +# Run flutter_rust_bridge code generation (if modifying Rust code) +cd rust +flutter_rust_bridge_codegen \ + --rust-input src/lib.rs \ + --dart-output ../lib/src/rust/lib.dart + +# Format Dart code +dart format . + +# Format Rust code +cd rust +cargo fmt + +# Analyze Dart code +flutter analyze + +# Run tests +flutter test +``` + +--- + +## Project Structure + +``` +convex_flutter/ +├── lib/ # Dart source code +│ ├── convex_flutter.dart # Public API exports +│ ├── src/ +│ │ ├── convex_client.dart # Main client (platform-agnostic) +│ │ ├── impl/ +│ │ │ ├── convex_client_web.dart # Web implementation (pure Dart) +│ │ │ └── convex_client_native.dart # Native implementation (FFI) +│ │ ├── rust/ # Generated FFI bindings +│ │ ├── convex_config.dart # Configuration class +│ │ ├── connection_status.dart +│ │ ├── app_lifecycle_*.dart +│ │ └── ... # Other Dart utilities +│ +├── rust/ # Rust source code (native platforms) +│ ├── Cargo.toml # Rust dependencies +│ ├── src/ +│ │ ├── lib.rs # Main Rust implementation +│ │ └── frb_generated.rs # Generated FFI code +│ └── target/ # Build artifacts +│ +├── example/ # Example Flutter app +│ ├── lib/main.dart # Example app code +│ ├── android/ # Android configuration +│ ├── ios/ # iOS configuration +│ ├── macos/ # macOS configuration +│ ├── web/ # Web configuration +│ └── ... +│ +├── test/ # Unit tests +├── ARCHITECTURE.md # Architecture documentation +├── PLATFORM_CONFIGURATION.md # Platform setup guide +├── CHANGELOG.md # Version history +└── README.md # Main documentation +``` + +--- + +## Making Changes + +### Branching Strategy + +- `main` - Stable release branch +- `develop` - Development branch (if used) +- `feature/*` - New features +- `fix/*` - Bug fixes +- `docs/*` - Documentation improvements +- `refactor/*` - Code refactoring + +### Commit Messages + +Write clear, descriptive commit messages following this format: + +``` +type(scope): Brief description + +Detailed explanation of changes (optional) + +Fixes #issue_number (if applicable) +``` + +**Types**: +- `feat`: New feature +- `fix`: Bug fix +- `docs`: Documentation changes +- `style`: Code style changes (formatting, etc.) +- `refactor`: Code refactoring +- `test`: Adding or updating tests +- `chore`: Maintenance tasks + +**Examples**: +``` +feat(web): Add web platform support with pure Dart implementation + +Implemented Convex WebSocket protocol in pure Dart for web platform. +Includes UUID generation, protocol messages, and subscription handling. + +Fixes #123 +``` + +``` +fix(macos): Add missing network entitlements + +Added com.apple.security.network.client entitlement to fix +WebSocket connection issues on macOS. + +Fixes #456 +``` + +--- + +## Testing + +### Running Tests + +```bash +# Run all tests +flutter test + +# Run specific test file +flutter test test/convex_client_test.dart + +# Run tests with coverage +flutter test --coverage +``` + +### Manual Testing + +**Web Platform**: +```bash +cd example +flutter run -d chrome +# Test all features in the browser +``` + +**Native Platforms**: +```bash +cd example + +# macOS +flutter run -d macos + +# iOS (requires macOS + Xcode) +flutter run -d ios + +# Android (requires Android device/emulator) +flutter run -d android +``` + +### Test Checklist for Pull Requests + +Before submitting a PR, verify: + +- [ ] All existing tests pass +- [ ] New features have tests +- [ ] Manual testing completed on relevant platforms: + - [ ] Web (if web-related changes) + - [ ] At least one native platform (if native changes) +- [ ] No breaking changes (or clearly documented) +- [ ] Documentation updated (if API changes) +- [ ] CHANGELOG.md updated (for notable changes) + +--- + +## Submitting Changes + +### Pull Request Process + +1. **Update Documentation**: If you changed APIs, update: + - README.md + - Inline code documentation + - ARCHITECTURE.md (if architectural changes) + - PLATFORM_CONFIGURATION.md (if platform-specific changes) + +2. **Update CHANGELOG.md**: Add entry under "Unreleased" section: + ```markdown + ## Unreleased + + ### New Features + - Your feature description + + ### Bug Fixes + - Your fix description + ``` + +3. **Create Pull Request** with: + - **Clear title**: `feat: Add web platform support` + - **Description**: Explain what, why, and how + - **Issue reference**: `Fixes #123` or `Closes #456` + - **Screenshots/GIFs**: For UI changes + - **Testing notes**: How you tested the changes + - **Breaking changes**: Clearly marked if any + +4. **Respond to Reviews**: Address feedback promptly and respectfully + +5. **CI/CD Checks**: Ensure all automated checks pass + +### PR Template + +```markdown +## Description +Brief description of changes + +## Type of Change +- [ ] Bug fix (non-breaking change fixing an issue) +- [ ] New feature (non-breaking change adding functionality) +- [ ] Breaking change (fix or feature that breaks existing functionality) +- [ ] Documentation update + +## Related Issue +Fixes #(issue number) + +## How Has This Been Tested? +Describe testing process + +## Platforms Tested +- [ ] Web +- [ ] Android +- [ ] iOS +- [ ] macOS +- [ ] Windows +- [ ] Linux + +## Checklist +- [ ] My code follows the project's style guidelines +- [ ] I have performed a self-review +- [ ] I have commented my code where needed +- [ ] I have updated documentation +- [ ] I have added tests +- [ ] All tests pass locally +- [ ] I have updated CHANGELOG.md +``` + +--- + +## Style Guidelines + +### Dart Code Style + +Follow the [Dart Style Guide](https://dart.dev/guides/language/effective-dart/style): + +```bash +# Format code +dart format . + +# Analyze code +flutter analyze +``` + +**Key conventions**: +- Use `lowerCamelCase` for variables, methods, parameters +- Use `UpperCamelCase` for classes, enums, typedefs +- Prefer `final` over `var` +- Use trailing commas for better formatting +- Document public APIs with `///` doc comments + +**Example**: +```dart +/// Executes a Convex query with the given [name] and [args]. +/// +/// Returns a JSON string containing the query result. +/// Throws [TimeoutException] if the operation exceeds [operationTimeout]. +/// +/// Example: +/// ```dart +/// final result = await client.query('users:list', {'limit': '10'}); +/// final users = jsonDecode(result); +/// ``` +Future query(String name, Map args) async { + // Implementation +} +``` + +### Rust Code Style + +Follow the [Rust Style Guide](https://doc.rust-lang.org/beta/style-guide/): + +```bash +cd rust +cargo fmt # Format +cargo clippy # Lint +``` + +**Key conventions**: +- Use `snake_case` for functions, variables +- Use `UpperCamelCase` for types, traits +- Document public items with `///` comments +- Use `Result` for error handling +- Prefer pattern matching over if/else + +--- + +## Platform-Specific Contributions + +### Working on Web Platform + +**File**: `lib/src/impl/convex_client_web.dart` + +**Dependencies**: `package:web`, `package:http` + +**No Rust required!** + +**Testing**: +```bash +flutter run -d chrome +flutter test # Tests run on VM, but web code path is used +``` + +**Key areas**: +- WebSocket protocol implementation +- UUID generation +- Connection state management +- Subscription handling + +### Working on Native Platforms + +**File**: `rust/src/lib.rs`, `lib/src/impl/convex_client_native.dart` + +**Dependencies**: Rust toolchain, `flutter_rust_bridge` + +**Testing**: Requires platform-specific setup (Xcode for iOS/macOS, Android SDK for Android, etc.) + +**Key areas**: +- FFI bridge between Dart and Rust +- Rust wrapper around Convex SDK +- Native platform configurations (entitlements, permissions) + +### Adding New Features + +When adding features: + +1. **Implement for both platforms** (web + native) if applicable +2. **Maintain API parity** between platforms +3. **Add tests** for both implementations +4. **Update documentation** in README.md +5. **Add platform-specific notes** in PLATFORM_CONFIGURATION.md if needed + +--- + +## Questions? + +- **Issues**: https://github.com/jkuldev/convex_flutter/issues +- **Discussions**: https://github.com/jkuldev/convex_flutter/discussions +- **Email**: Contact maintainers at jkuldev.com + +--- + +## License + +By contributing to `convex_flutter`, you agree that your contributions will be licensed under the MIT License. + +--- + +**Thank you for contributing to convex_flutter! 🎉** diff --git a/third_party/convex_flutter/ICARUS_PATCH.md b/third_party/convex_flutter/ICARUS_PATCH.md new file mode 100644 index 00000000..dc5fc8c8 --- /dev/null +++ b/third_party/convex_flutter/ICARUS_PATCH.md @@ -0,0 +1,36 @@ +# Icarus convex_flutter patch + +Source: the published `convex_flutter` 3.0.1 package. + +Icarus pins the package's native `convex` Rust client to 0.10.4. The published +package lock selected 0.10.2. Convex Rust 0.10.3 introduced the reconnect-state +repair and auth-token callback used to restore authenticated state after a +WebSocket reconnect; 0.10.4 includes that repair plus a subscription leak fix. + +The package's hand-written Rust auth adapter now gives the Dart token callback +to `ConvexClient.set_auth_callback`. That keeps token refresh in the same +upstream state machine that replays subscriptions and mutations after a socket +reconnect. The old adapter owned a separate expiry timer and called static +`set_auth`, which could leave the client disconnected after the server rejected +an expired token. + +Auth handles also carry an internal generation. Disposal only clears auth when +the handle still owns the current generation, so a delayed cancellation from a +replaced handle cannot erase the fresh callback. + +The native manual reconnect API calls the Icarus-patched `convex` 0.10.4 crate +in `../convex_rs`. It waits for a real connecting-to-connected transition; the +published package method only ran its configured health query. + +Generated Dart and Rust bridge files are never hand-edited. They are regenerated +from `rust/src/lib.rs` with `flutter_rust_bridge_codegen` 2.11.1. Hand-written +changes are limited to the Dart client implementation, `rust/src/lib.rs`, and +the local `convex` dependency in `rust/Cargo.toml`. + +```sh +cd third_party/convex_flutter/rust +flutter_rust_bridge_codegen generate +``` + +This directory can be removed once a published `convex_flutter` release uses a +Convex Rust client with the same fixes and passes the Icarus auth gauntlet. diff --git a/third_party/convex_flutter/LICENSE b/third_party/convex_flutter/LICENSE new file mode 100644 index 00000000..f70f0b3f --- /dev/null +++ b/third_party/convex_flutter/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 jkuldev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/third_party/convex_flutter/MIGRATION_v3.md b/third_party/convex_flutter/MIGRATION_v3.md new file mode 100644 index 00000000..23a53bba --- /dev/null +++ b/third_party/convex_flutter/MIGRATION_v3.md @@ -0,0 +1,403 @@ +# Migration Guide: v2.x → v3.0.0 + +## Overview + +**Good news: v3.0.0 has ZERO breaking changes!** 🎉 + +This is a feature release that adds web platform support while maintaining 100% backward compatibility with v2.x. Your existing code will continue to work without modifications. + +## What's New in v3.0.0 + +### Major New Features + +1. **Web Platform Support** 🌐 + - Full web platform support with pure Dart implementation + - No Rust required for web builds + - Same API works on web and native platforms + +2. **Platform-Specific Implementations** + - Automatic platform selection via conditional imports + - Web: Pure Dart WebSocket client + - Native: FFI + Rust SDK (unchanged) + +3. **Critical Bug Fixes** + - Fixed macOS connection issues (network entitlements) + - Fixed Android missing INTERNET permission + - Fixed Rust rustls CryptoProvider error + +## Migration Steps + +### Step 1: Update Package Version + +Update your `pubspec.yaml`: + +```yaml +dependencies: + convex_flutter: ^3.0.0 # Update from ^2.2.0 +``` + +Then run: + +```bash +flutter pub upgrade convex_flutter +``` + +### Step 2: Platform Configuration (One-Time Setup) + +#### macOS Apps + +Add network entitlements to **both** files: + +**macos/Runner/DebugProfile.entitlements**: +```xml +com.apple.security.network.client + +com.apple.security.network.server + +``` + +**macos/Runner/Release.entitlements**: +```xml +com.apple.security.network.client + +com.apple.security.network.server + +``` + +#### Android Apps + +Add internet permission to **android/app/src/main/AndroidManifest.xml**: + +```xml + + + + +``` + +#### iOS, Windows, Linux Apps + +No changes required - these platforms work out of the box. + +### Step 3: Test Your App + +```bash +# Test on your target platforms +flutter run -d chrome # Web +flutter run -d macos # macOS +flutter run -d android # Android +flutter run -d ios # iOS +``` + +### Step 4: Build for Web (New!) + +You can now build your app for web: + +```bash +flutter build web +``` + +**No Rust toolchain required for web builds!** + +--- + +## Code Changes Required + +### None! ✅ + +Your existing v2.x code will continue to work without modifications. The API is 100% compatible. + +**Example - This code works identically in v2.x and v3.0.0**: + +```dart +import 'package:convex_flutter/convex_flutter.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialization - no changes + await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: 'https://my-app.convex.cloud', + clientId: 'flutter-app-1.0', + ), + ); + + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + final client = ConvexClient.instance; + + return MaterialApp( + home: Scaffold( + body: StreamBuilder( + stream: client.connectionState, + builder: (context, snapshot) { + // Works on both web and native! + final isConnected = snapshot.data == WebSocketConnectionState.connected; + return Text(isConnected ? 'Connected' : 'Connecting...'); + }, + ), + ), + ); + } +} +``` + +--- + +## Platform-Specific Differences + +### Web vs Native + +While the API is identical, there are minor implementation differences: + +| Feature | Web | Native | +|---------|-----|--------| +| WebSocket Source | Browser WebSocket API | Convex Rust SDK | +| Implementation | Pure Dart | FFI + Rust | +| Build Requirements | None | Rust toolchain | +| Performance | Excellent | Excellent | +| API | **Identical** | **Identical** | + +**Bottom Line**: Your code doesn't need to know which platform it's running on. The package handles it automatically. + +--- + +## Deprecations + +No APIs were deprecated in v3.0.0. All v2.x methods remain available. + +--- + +## New Capabilities + +### Web Platform Support + +You can now target web alongside mobile and desktop: + +```bash +# Web (new in v3.0.0) +flutter build web + +# Mobile (existing) +flutter build apk +flutter build ios + +# Desktop (existing) +flutter build macos +flutter build windows +flutter build linux +``` + +### Cross-Platform Example App + +The example app now works on **all platforms**: + +```bash +cd example + +# Run on any platform +flutter run -d chrome # Web +flutter run -d macos # macOS +flutter run -d ios # iOS Simulator +flutter run -d android # Android Emulator +flutter run -d windows # Windows +flutter run -d linux # Linux +``` + +--- + +## Troubleshooting + +### macOS: Stuck in "Connecting" + +**Symptom**: App builds successfully but connection state never changes from "connecting" + +**Solution**: Add network entitlements (see Step 2 above) + +**Verify**: +```bash +# Check DebugProfile.entitlements contains: +grep "network.client" macos/Runner/DebugProfile.entitlements +``` + +### Android: Network Security Exception + +**Symptom**: App crashes with `SocketException: Permission denied` + +**Solution**: Add INTERNET permission (see Step 2 above) + +**Verify**: +```bash +# Check AndroidManifest.xml contains: +grep "INTERNET" android/app/src/main/AndroidManifest.xml +``` + +### Web: Build Errors + +**Symptom**: Build fails when targeting web + +**Solution**: Ensure Flutter web support is enabled: + +```bash +flutter config --enable-web +flutter clean +flutter pub get +flutter build web +``` + +### Rust CryptoProvider Error + +**Symptom**: `Could not automatically determine the process-level CryptoProvider` + +**Solution**: This was fixed in the package. Upgrade to v3.0.0: + +```bash +flutter pub upgrade convex_flutter +``` + +--- + +## Performance Considerations + +### Build Times + +**Web**: Faster builds (no Rust compilation) +```bash +# First build +flutter build web # ~1-2 minutes + +# Subsequent builds +flutter build web # ~30-60 seconds +``` + +**Native**: Unchanged from v2.x +```bash +# First build (includes Rust compilation) +flutter build apk # ~3-5 minutes + +# Subsequent builds (Rust cached) +flutter build apk # ~1-2 minutes +``` + +### Runtime Performance + +Both web and native implementations have excellent performance: + +- **Web**: Leverages browser's native WebSocket engine +- **Native**: Uses compiled Rust code + +**No performance degradation** compared to v2.x. + +--- + +## Testing Recommendations + +### Minimum Testing + +Before deploying v3.0.0, test on: + +- [ ] Your primary target platform (web, iOS, Android, etc.) +- [ ] Connection establishment +- [ ] Query execution +- [ ] Mutation execution +- [ ] Subscriptions (if you use them) +- [ ] Authentication (if you use it) + +### Comprehensive Testing + +For production apps, also test: + +- [ ] Connection state monitoring +- [ ] Reconnection after network interruption +- [ ] App backgrounding/foregrounding +- [ ] Hot reload (development) +- [ ] Release builds + +--- + +## Rollback Plan + +If you encounter issues with v3.0.0, you can easily rollback: + +```yaml +# pubspec.yaml +dependencies: + convex_flutter: ^2.2.0 # Rollback to v2.2.0 +``` + +Then run: + +```bash +flutter pub downgrade convex_flutter +flutter clean +flutter pub get +``` + +**Note**: You'll lose web platform support and the bug fixes when rolling back. + +--- + +## Support + +If you encounter migration issues: + +1. **Check Documentation**: + - [PLATFORM_CONFIGURATION.md](PLATFORM_CONFIGURATION.md) - Platform setup guide + - [README.md](README.md) - Updated with v3.0.0 features + - [ARCHITECTURE.md](ARCHITECTURE.md) - Web implementation details + +2. **Search Issues**: https://github.com/jkuldev/convex_flutter/issues + +3. **Create New Issue**: https://github.com/jkuldev/convex_flutter/issues/new + - Include Flutter version, platform, and error details + +--- + +## Changelog + +For complete v3.0.0 changes, see [CHANGELOG.md](CHANGELOG.md#300). + +**Summary**: +- ✅ Web platform support (pure Dart) +- ✅ Fixed macOS network permissions +- ✅ Fixed Android INTERNET permission +- ✅ Fixed Rust rustls CryptoProvider +- ✅ Updated Convex SDK to 0.10.2 +- ✅ Zero breaking changes + +--- + +## Next Steps + +After migrating to v3.0.0: + +1. **Enable Web** (optional): + ```bash + flutter config --enable-web + flutter run -d chrome + ``` + +2. **Review New Documentation**: + - Platform-specific setup in PLATFORM_CONFIGURATION.md + - Web implementation details in ARCHITECTURE.md + +3. **Enjoy Multi-Platform Support**: Build your Convex Flutter app for web, mobile, and desktop! + +--- + +**Migration Difficulty**: ⭐ Very Easy (no code changes required) + +**Time Required**: 5-10 minutes (mostly platform configuration) + +**Risk Level**: 🟢 Low (backward compatible, easy rollback) + +--- + +**Questions?** Open an issue: https://github.com/jkuldev/convex_flutter/issues diff --git a/third_party/convex_flutter/PLATFORM_CONFIGURATION.md b/third_party/convex_flutter/PLATFORM_CONFIGURATION.md new file mode 100644 index 00000000..54f73e7c --- /dev/null +++ b/third_party/convex_flutter/PLATFORM_CONFIGURATION.md @@ -0,0 +1,264 @@ +# Platform Configuration Guide + +This guide explains the platform-specific configuration required for `convex_flutter` to work correctly on all supported platforms. + +## Quick Reference + +| Platform | Configuration Required | Auto-configured? | +|----------|----------------------|------------------| +| **Web** | None | ✅ Yes | +| **iOS** | None | ✅ Yes | +| **macOS** | Network entitlements | ❌ Manual setup required | +| **Android** | INTERNET permission | ❌ Manual setup required | +| **Windows** | None | ✅ Yes | +| **Linux** | None | ✅ Yes | + +--- + +## Platform-Specific Setup + +### macOS + +macOS apps use App Sandbox for security, which requires explicit network permissions. + +#### Required Files + +1. **DebugProfile.entitlements** (for debug builds) +2. **Release.entitlements** (for release builds) + +**Location**: `macos/Runner/` + +#### Configuration + +Add the following entitlements to **both** files: + +```xml + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + +``` + +#### Critical Permissions + +- `com.apple.security.network.client` - **Required** for outgoing WebSocket connections to Convex +- `com.apple.security.network.server` - **Required** for accepting incoming connections (if needed) +- `com.apple.security.app-sandbox` - Enables macOS App Sandbox +- `com.apple.security.cs.allow-jit` - Allows JIT compilation (required for Flutter) + +#### What Happens Without These? + +Without `com.apple.security.network.client`, your app will: +- Build and launch successfully +- Get stuck in "connecting" state forever +- Never establish WebSocket connection to Convex +- Show no error messages (silently blocked by macOS sandbox) + +--- + +### Android + +Android requires explicit permission for internet access. + +#### Required File + +**AndroidManifest.xml** + +**Location**: `android/app/src/main/AndroidManifest.xml` + +#### Configuration + +Add the INTERNET permission **inside the `` tag, before ``**: + +```xml + + + + +``` + +#### Permission Details + +- **Type**: Normal permission (auto-granted at install) +- **User prompt**: No - granted automatically +- **Required for**: All network operations (WebSocket, HTTP, etc.) + +#### What Happens Without This? + +Without `android.permission.INTERNET`, your app will: +- Build successfully +- Crash or fail when attempting network connections +- Show security exceptions in logs + +--- + +### iOS + +**No configuration required** ✅ + +iOS apps have network access by default unless explicitly restricted. `convex_flutter` works out of the box on iOS. + +**Note**: If you're using App Transport Security (ATS) customization, ensure your Convex backend URL is allowed. + +--- + +### Web + +**No configuration required** ✅ + +Web platform uses the browser's native WebSocket API, which inherits the browser's network permissions. Works automatically. + +**Technical Details**: +- Uses pure Dart implementation (no FFI) +- Leverages `package:web` for WebSocket access +- Respects browser CORS and security policies + +--- + +### Windows + +**No configuration required** ✅ + +Windows desktop apps have network access by default. The Windows Firewall may prompt users to allow network access on first run (standard Windows behavior). + +--- + +### Linux + +**No configuration required** ✅ + +Linux desktop apps have network access by default. No special permissions or configuration needed. + +--- + +## Troubleshooting + +### macOS: Stuck in "Connecting" State + +**Symptoms**: +- App builds and launches +- Connection state shows "connecting" forever +- No error messages + +**Solution**: +1. Check `macos/Runner/DebugProfile.entitlements` +2. Ensure `com.apple.security.network.client` is present +3. Check `macos/Runner/Release.entitlements` for release builds +4. Clean build: `flutter clean && flutter run` + +### Android: Network Security Exception + +**Symptoms**: +- App crashes on connection attempt +- Error: `java.net.SocketException: Permission denied` +- Logs show security policy violation + +**Solution**: +1. Check `android/app/src/main/AndroidManifest.xml` +2. Add `` +3. Rebuild: `flutter clean && flutter run` + +### Rust Panic: CryptoProvider Error + +**Symptoms**: +- Error: `Could not automatically determine the process-level CryptoProvider` +- Panic in rustls library + +**Solution**: +This affects the package itself, not user apps. If you encounter this: +1. Check `rust/Cargo.toml` +2. Ensure convex dependency does NOT have `default-features = false` +3. Correct format: `convex = { version = "0.10", features = ["rustls-tls-webpki-roots"] }` + +--- + +## Integration Checklist + +When integrating `convex_flutter` into your Flutter app, verify: + +- [ ] **macOS**: Added network entitlements to both DebugProfile and Release entitlements +- [ ] **Android**: Added INTERNET permission to AndroidManifest.xml +- [ ] **iOS**: No action required (works by default) +- [ ] **Web**: No action required (works by default) +- [ ] **Windows**: No action required (works by default) +- [ ] **Linux**: No action required (works by default) + +--- + +## Why These Permissions Are Needed + +### macOS App Sandbox + +macOS uses a security feature called "App Sandbox" that restricts app capabilities by default. Apps must explicitly declare what they need to access (network, files, camera, etc.). This is a macOS platform requirement, not specific to `convex_flutter`. + +**Learn more**: [Apple: App Sandbox](https://developer.apple.com/documentation/security/app_sandbox) + +### Android Permission System + +Android uses a permission-based security model where apps must declare all permissions they'll use. Network access is considered a "normal" permission (auto-granted) but must still be declared in the manifest. + +**Learn more**: [Android: App Permissions](https://developer.android.com/guide/topics/permissions/overview) + +--- + +## Example Apps + +The `example/` directory in this repository demonstrates proper configuration for all platforms: + +``` +example/ +├── android/app/src/main/AndroidManifest.xml # INTERNET permission +├── ios/ # No config needed +├── macos/Runner/ +│ ├── DebugProfile.entitlements # Network entitlements +│ └── Release.entitlements # Network entitlements +├── web/ # No config needed +├── windows/ # No config needed +└── linux/ # No config needed +``` + +--- + +## Platform Support Matrix + +| Platform | SDK Version | Network Config | Rust Required | +|----------|-------------|----------------|---------------| +| Web | Any | None | No | +| iOS | iOS 12+ | None | Yes (build-time) | +| macOS | macOS 10.14+ | Entitlements | Yes (build-time) | +| Android | API 21+ | Manifest | Yes (build-time) | +| Windows | Windows 7+ | None | Yes (build-time) | +| Linux | Any | None | Yes (build-time) | + +**Note**: Rust is required at **build time** for native platforms (iOS, macOS, Android, Windows, Linux) but **not required** for web platform. + +--- + +## Questions or Issues? + +If you encounter platform-specific issues not covered here: + +1. Check the [example app configuration](example/) +2. Search [GitHub issues](https://github.com/get-convex/convex_flutter/issues) +3. Create a new issue with: + - Platform and version + - Flutter doctor output + - Relevant configuration files + - Error messages or logs + +--- + +**Last Updated**: 2026-01-10 +**Package Version**: 3.0.0 diff --git a/third_party/convex_flutter/PUB_DEPLOY_GUIDE.md b/third_party/convex_flutter/PUB_DEPLOY_GUIDE.md new file mode 100644 index 00000000..e8e9fa2b --- /dev/null +++ b/third_party/convex_flutter/PUB_DEPLOY_GUIDE.md @@ -0,0 +1,332 @@ +# Pub.dev Deployment Guide - convex_flutter v2.2.0 + +## Pre-Deployment Checklist + +### ✅ All Requirements Met + +- [x] Version bumped to 2.2.0 in pubspec.yaml +- [x] CHANGELOG.md updated with detailed v2.2.0 release notes +- [x] README.md updated with all new features documented +- [x] LICENSE file present (MIT License) +- [x] Package validation passed (`flutter pub publish --dry-run`) +- [x] All commits pushed to GitHub +- [x] Example app working and demonstrating all features + +### Package Information + +**Package Name**: `convex_flutter` +**Version**: `2.2.0` +**Repository**: https://github.com/jkuldev/convex_flutter +**Homepage**: https://jkuldev.com +**License**: MIT +**Package Size**: 328 KB (compressed) + +## What's New in v2.2.0 + +### Major Features + +1. **Real-Time WebSocket Connection State Monitoring** + - `connectionState` stream for real-time updates + - `currentConnectionState` getter for sync access + - `isConnected` boolean getter + - Automatic state transitions (Connecting → Connected) + +2. **Critical Bug Fixes** + - Fixed race condition in WebSocket connection initialization + - Fixed connection state stuck on "connecting" + - Improved connection reliability + +3. **Enhanced Documentation** + - Health check query setup guide (TypeScript + Dart) + - Comprehensive usage examples with StreamBuilder + - Clear optional vs required patterns + - Step-by-step tutorials + +### API Additions + +```dart +// New in v2.2.0 +Stream connectionState +WebSocketConnectionState currentConnectionState +bool isConnected +``` + +### Deprecated APIs + +```dart +// Deprecated (still works, but use connectionState instead) +Future checkConnection() +``` + +## Deployment Steps + +### Step 1: Final Validation + +Run the dry-run command to verify everything is ready: + +```bash +flutter pub publish --dry-run +``` + +**Expected Output:** +- Package validation passed +- 0 warnings +- 1 hint about version increment (this is fine) +- Total compressed size: ~328 KB + +### Step 2: Verify Git Status + +Make sure all changes are committed: + +```bash +git status +git log --oneline -5 +``` + +**Expected Commits on Branch:** +``` +5218369 chore: Bump version to 2.2.0 for pub.dev release +ce51ed4 docs: Clarify health check is optional but recommended +0e7484c docs: Recommend dedicated health check query (health:ping) +8aa9d6e example updated +3c34666 feat: Add real-time WebSocket connection state monitoring (v2.2.0) +``` + +### Step 3: Push to GitHub + +Push the branch to GitHub: + +```bash +git push -u origin fix/websocket-connection-state-v2.2.0 +``` + +**Or if using HTTPS:** +```bash +git remote set-url origin https://github.com/jkuldev/convex_flutter.git +git push -u origin fix/websocket-connection-state-v2.2.0 +``` + +### Step 4: Merge to Main + +Option A - Via GitHub Pull Request: +1. Go to https://github.com/jkuldev/convex_flutter/pulls +2. Create Pull Request from `fix/websocket-connection-state-v2.2.0` +3. Review changes +4. Merge to main +5. Pull main locally: `git checkout main && git pull` + +Option B - Local Merge: +```bash +git checkout main +git merge fix/websocket-connection-state-v2.2.0 +git push origin main +``` + +### Step 5: Create Git Tag (Recommended) + +```bash +git tag v2.2.0 +git push origin v2.2.0 +``` + +Or create annotated tag with release notes: +```bash +git tag -a v2.2.0 -m "Release v2.2.0: WebSocket Connection State Monitoring + +- Real-time WebSocket connection state streams +- Fixed critical race condition in connection initialization +- Fixed connection state stuck on 'connecting' +- Enhanced documentation with health check guide +- New connection state APIs +- Comprehensive example app with 5 screens" + +git push origin v2.2.0 +``` + +### Step 6: Publish to pub.dev + +**IMPORTANT**: Make sure you're on the main branch with the latest changes: + +```bash +git checkout main +git pull +``` + +**Publish the package:** + +```bash +flutter pub publish +``` + +**The command will:** +1. Validate the package +2. Show a preview of what will be published +3. Ask for confirmation +4. Upload to pub.dev + +**You'll need:** +- A verified pub.dev account +- Access credentials (you'll be prompted to login) + +**After Publishing:** +- Package will be available at: https://pub.dev/packages/convex_flutter +- Version 2.2.0 will appear within minutes + +### Step 7: Verify Publication + +After publishing, verify on pub.dev: + +1. Visit: https://pub.dev/packages/convex_flutter +2. Check version shows as 2.2.0 +3. Verify README displays correctly +4. Check CHANGELOG is visible +5. Confirm example tab shows code +6. Review package score (should be 130+/140) + +## Post-Deployment + +### Create GitHub Release + +1. Go to: https://github.com/jkuldev/convex_flutter/releases/new +2. Choose tag: `v2.2.0` +3. Release title: `v2.2.0 - WebSocket Connection State Monitoring` +4. Description: Copy from CHANGELOG.md or use: + +```markdown +## 🎉 convex_flutter v2.2.0 + +### New Features +- **Real-Time WebSocket Connection State**: Monitor connection status via reactive streams +- `connectionState` stream for real-time updates +- `currentConnectionState` and `isConnected` getters +- Automatic state transitions + +### Bug Fixes +- Fixed critical race condition in WebSocket connection initialization +- Fixed connection state stuck on "connecting" +- Improved connection reliability + +### Documentation +- Comprehensive health check guide with TypeScript examples +- WebSocket connection state usage examples +- Enhanced example app with 5 demonstration screens + +[View Full Changelog](https://github.com/jkuldev/convex_flutter/blob/main/CHANGELOG.md) + +**Install:** +```yaml +dependencies: + convex_flutter: ^2.2.0 +``` +``` + +5. Publish release + +### Announce (Optional) + +Consider announcing the release: +- Twitter/X +- LinkedIn +- Flutter community Discord/Slack +- Reddit r/FlutterDev +- Dev.to blog post + +## Troubleshooting + +### Issue: "Unauthorized" error when publishing + +**Solution:** +```bash +# Login to pub.dev +dart pub login + +# Then try publishing again +flutter pub publish +``` + +### Issue: "Version already exists" + +**Solution:** +- Version 2.2.0 is already published +- Increment version to 2.2.1 or 2.3.0 +- Update CHANGELOG.md +- Commit and try again + +### Issue: Package validation fails + +**Solution:** +```bash +# Run dry-run to see specific errors +flutter pub publish --dry-run + +# Fix any errors shown +# Common issues: +# - Missing README.md +# - Missing CHANGELOG.md +# - Invalid pubspec.yaml +# - Missing LICENSE +``` + +### Issue: Git push fails (permission denied) + +**Solution:** +```bash +# Use HTTPS instead of SSH +git remote set-url origin https://github.com/jkuldev/convex_flutter.git + +# Or set up SSH keys: +ssh-keygen -t ed25519 -C "your_email@example.com" +# Add to GitHub: https://github.com/settings/keys +``` + +## Rollback Plan + +If issues are discovered after publishing: + +### Option 1: Publish Hotfix (Recommended) + +```bash +# Fix the issue +# Update version to 2.2.1 +# Update CHANGELOG.md +git commit -am "fix: Critical issue in v2.2.0" +flutter pub publish +``` + +### Option 2: Retract Version (Last Resort) + +```bash +# This marks the version as broken +dart pub publisher retract convex_flutter 2.2.0 +``` + +**Note:** Retraction doesn't delete the package, it just warns users. + +## Support After Release + +Monitor for issues: +- GitHub Issues: https://github.com/jkuldev/convex_flutter/issues +- pub.dev comments +- Stack Overflow questions tagged `convex-flutter` + +## Success Criteria + +✅ Package published successfully +✅ Version 2.2.0 visible on pub.dev +✅ Documentation renders correctly +✅ Example app accessible via pub.dev +✅ Package score 130+/140 +✅ GitHub release created with tag v2.2.0 +✅ All features working as documented + +## Contact + +If you encounter any issues during deployment: +- Check pub.dev documentation: https://dart.dev/tools/pub/publishing +- Flutter pub publishing guide: https://flutter.dev/docs/development/packages-and-plugins/developing-packages + +--- + +**Ready to publish!** 🚀 + +Run: `flutter pub publish` diff --git a/third_party/convex_flutter/README.md b/third_party/convex_flutter/README.md new file mode 100644 index 00000000..ce0eed95 --- /dev/null +++ b/third_party/convex_flutter/README.md @@ -0,0 +1,493 @@ +# Convex Flutter + +

+ + + + + + + + + +
Home ScreenMessaging Screen
Home ScreenReal-time Messaging
+

+ +A Flutter plugin for integrating with the Convex backend. It provides a simple Dart API over the Convex Rust core to run queries, mutations, and actions, and to subscribe to real-time updates. + +This package wraps the [Convex Rust library](https://github.com/get-convex/convex-rs) and exposes a Flutter-friendly interface. + +## Features + +- Real-time subscriptions to Convex queries +- Simple Dart API for queries, mutations, and actions +- Authentication with automatic token refresh +- Auth state stream for reactive UI updates +- **WebSocket connection state** - Real-time connection status monitoring via streams +- **Operation timeouts** - Configurable timeout for all queries, mutations, and actions +- **Lifecycle monitoring** - Stream of app lifecycle events (foreground/background) +- **Connection management** - Manual connection checking and reconnect functionality +- **Singleton pattern** - Access client anywhere via `ConvexClient.instance` +- **Multi-platform support** - Works on Web (pure Dart), Android, iOS, macOS, Windows, and Linux (FFI) + +## Installation + +Add the package to your Flutter project: + +```bash +flutter pub add convex_flutter +``` + +That's it! The health check query mentioned below is optional - you can start using the SDK immediately without it. + +## Platform Configuration + +**Important**: Some platforms require additional configuration for network access. This is a one-time setup. + +| Platform | Configuration Required | +|----------|------------------------| +| **Web** | ✅ None - works automatically | +| **iOS** | ✅ None - works automatically | +| **macOS** | ⚠️ **Network entitlements required** | +| **Android** | ⚠️ **INTERNET permission required** | +| **Windows** | ✅ None - works automatically | +| **Linux** | ✅ None - works automatically | + +### Quick Setup + +**macOS**: Add network entitlements to `macos/Runner/DebugProfile.entitlements` and `Release.entitlements`: + +```xml +com.apple.security.network.client + +com.apple.security.network.server + +``` + +**Android**: Add internet permission to `android/app/src/main/AndroidManifest.xml`: + +```xml + +``` + +**📖 See [PLATFORM_CONFIGURATION.md](PLATFORM_CONFIGURATION.md) for complete setup instructions and troubleshooting.** + +## Requirements + +- Dart SDK >= 3.8.1 and Flutter >= 3.3.0 +- **Web platform**: No additional requirements (uses pure Dart implementation) +- **Native platforms** (Android, iOS, macOS, Windows, Linux): + - Rust toolchain (rustup + cargo) for building native code + - Platform-specific toolchains: + - Android: JDK 11 and Android SDK/NDK + - iOS/macOS: Xcode and CocoaPods + - Windows: Visual Studio Build Tools (C++) + - Linux: clang, pkg-config, and build essentials + +## Quick start + +### Optional: Create a Health Check Query (Recommended) + +For connection monitoring and health checks, it's recommended to create a lightweight health check query in your Convex backend. This is **optional** but provides a clean way to verify connectivity without side effects. + +Create a file `convex/health.ts` in your Convex backend: + +```typescript +// convex/health.ts +import { query } from "./_generated/server"; + +export const ping = query({ + args: {}, + handler: async () => { + return "ok"; + }, +}); +``` + +This creates a lightweight endpoint at `health:ping` that you can use for connection health checks. It has no side effects and returns instantly. + +### Initialize the Client + +```dart +import 'package:convex_flutter/convex_flutter.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // Initialize the client once (singleton) + await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: 'https://my-app.convex.cloud', + clientId: 'flutter-app-1.0', + operationTimeout: Duration(seconds: 30), // Optional, defaults to 30s + healthCheckQuery: 'health:ping', // Optional, for connection checks (requires health.ts) + ), + ); + + runApp(MyApp()); +} + +// Access the client anywhere in your app +void example() async { + final client = ConvexClient.instance; + + // Optional: authenticate (see Authentication section below) + await client.setAuth(token: 'YOUR_AUTH_TOKEN'); + + // Query (with timeout) + try { + final users = await client.query('users:list', {'limit': '10'}); + print('Users: $users'); + } on TimeoutException { + print('Connection timeout!'); + } + + // Subscribe to real-time updates + final sub = await client.subscribe( + name: 'messages:list', + args: {}, + onUpdate: (value) => print('Update: $value'), + onError: (message, value) => print('Error: $message ${value ?? ''}'), + ); + + // Mutation + await client.mutation( + name: 'messages:send', + args: {'body': 'Hello!', 'author': 'User123'}, + ); + + // Action (if you have actions defined) + // final res = await client.action(name: 'files:upload', args: {...}); + + // Later, when done + sub.cancel(); +} +``` + +## Authentication + +The SDK provides comprehensive authentication support for Convex backends. + +### Simple Token Authentication + +For basic scenarios or testing, set a static JWT token: + +```dart +// Set authentication +await client.setAuth(token: 'your-jwt-token'); + +// Clear authentication +await client.setAuth(token: null); +``` + +### Automatic Token Refresh (Recommended) + +For production apps, use `setAuthWithRefresh` which automatically refreshes tokens 60 seconds before they expire: + +```dart +final authHandle = await client.setAuthWithRefresh( + fetchToken: () async { + // Return JWT from your auth provider (Firebase, Clerk, Auth0, etc.) + return await FirebaseAuth.instance.currentUser?.getIdToken(); + }, + onAuthChange: (isAuthenticated) { + print('Auth state: $isAuthenticated'); + }, +); + +// When signing out, dispose the auth handle +authHandle.dispose(); +``` + +### Auth State Stream + +Listen to authentication state changes reactively: + +```dart +client.authState.listen((isAuthenticated) { + setState(() => _isLoggedIn = isAuthenticated); +}); +``` + +### Sync Auth Check + +Check current auth state synchronously: + +```dart +if (client.isAuthenticated) { + // User is authenticated +} +``` + +### Clear Authentication + +Clear auth and stop any running token refresh: + +```dart +await client.clearAuth(); +``` + +## Connection Management + +The SDK provides tools for managing connection state and handling network interruptions. + +### Operation Timeouts + +All queries, mutations, and actions have configurable timeouts (default: 30 seconds): + +```dart +await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: 'https://my-app.convex.cloud', + operationTimeout: Duration(seconds: 45), // Custom timeout + ), +); + +// Operations will throw TimeoutException if they exceed the timeout +try { + await ConvexClient.instance.query('slowQuery', {}); +} on TimeoutException { + print('Operation timed out!'); +} +``` + +### Real-Time WebSocket Connection State (Recommended) + +Monitor WebSocket connection state in real-time using streams. This is the recommended approach for connection monitoring: + +```dart +// Listen to connection state changes +ConvexClient.instance.connectionState.listen((state) { + switch (state) { + case WebSocketConnectionState.connected: + print('WebSocket connected!'); + // Update UI, enable features + break; + case WebSocketConnectionState.connecting: + print('WebSocket connecting...'); + // Show loading indicator + break; + } +}); + +// Or use in a StreamBuilder for reactive UI +StreamBuilder( + stream: ConvexClient.instance.connectionState, + initialData: ConvexClient.instance.currentConnectionState, + builder: (context, snapshot) { + final state = snapshot.data ?? WebSocketConnectionState.connecting; + final isConnected = state == WebSocketConnectionState.connected; + + return Chip( + avatar: Icon(isConnected ? Icons.cloud_done : Icons.cloud_sync), + label: Text(isConnected ? 'Connected' : 'Connecting'), + backgroundColor: isConnected ? Colors.green : Colors.orange, + ); + }, +) + +// Synchronous access to current state +if (ConvexClient.instance.isConnected) { + // WebSocket is connected +} +``` + +**Features:** +- Real-time state updates via Stream (no polling needed) +- Automatic state transitions when WebSocket connects/disconnects +- Synchronous getter for immediate state access +- Works across all platforms + +**Note:** The WebSocket connection is established lazily when the first operation (query, mutation, subscribe, action) is executed. + +**Optional: Auto-Connect on Startup** + +To establish the connection immediately when your app starts (recommended for better UX), trigger a lightweight query in your app's initialization. Using a dedicated health check query is the cleanest approach: + +**1. Create a health check query in your Convex backend (optional but recommended):** + +```typescript +// convex/health.ts +import { query } from "./_generated/server"; + +export const ping = query({ + args: {}, + handler: async () => { + return "ok"; + }, +}); +``` + +**2. Trigger it on app startup:** + +```dart +// In your home screen or app initialization +@override +void initState() { + super.initState(); + // Trigger connection immediately with health check + ConvexClient.instance.query('health:ping', {}); +} +``` + +**Alternative:** You can use any existing lightweight query instead of creating a dedicated health check: + +```dart +// Use any existing query to trigger connection +ConvexClient.instance.query('users:list', {'limit': '1'}); +``` + +### Manual Connection Check (Deprecated) + +For backward compatibility, you can check connection status manually using a health check query: + +```dart +// Configure a lightweight query for health checks +await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: 'https://my-app.convex.cloud', + healthCheckQuery: 'health:ping', // Lightweight health check query + ), +); + +// Check connection status (deprecated - use connectionState stream instead) +final status = await ConvexClient.instance.checkConnection(); + +switch (status) { + case ConnectionStatus.connected: + print('Connected!'); + case ConnectionStatus.timeout: + print('Connection timeout'); + case ConnectionStatus.error: + print('Connection error'); + case ConnectionStatus.unknown: + print('Not checked yet'); +} +``` + +### Manual Reconnect + +Trigger reconnection attempt manually: + +```dart +final connected = await ConvexClient.instance.reconnect(); +if (connected) { + print('Reconnected successfully'); +} +``` + +## Lifecycle Monitoring + +Monitor app lifecycle events to handle foreground/background transitions. + +### Listen to Lifecycle Events + +```dart +ConvexClient.instance.lifecycleEvents.listen((event) { + print('App lifecycle: $event'); + + if (event == AppLifecycleEvent.resumed) { + // App came to foreground + // Optionally reconnect or refresh data + ConvexClient.instance.reconnect(); + } + + if (event == AppLifecycleEvent.paused) { + // App went to background + // Optionally pause polling or save state + } +}); +``` + +### Lifecycle Events + +- `AppLifecycleEvent.resumed` - App in foreground +- `AppLifecycleEvent.paused` - App in background +- `AppLifecycleEvent.inactive` - App inactive (e.g., during phone call) +- `AppLifecycleEvent.detached` - App being terminated + +## API overview + +| Method | Description | +|--------|-------------| +| `ConvexClient.initialize(ConvexConfig)` | Initialize singleton client with configuration | +| `ConvexClient.instance` | Access singleton instance anywhere | +| `query(name, args)` | Execute a query with timeout, returns JSON string | +| `mutation({ name, args })` | Execute a mutation with timeout, returns JSON string | +| `action({ name, args })` | Execute an action with timeout, returns JSON string | +| `subscribe({ name, args, onUpdate, onError })` | Subscribe to real-time updates, returns `SubscriptionHandle` | +| `setAuth({ token })` | Set or clear static auth token | +| `setAuthWithRefresh({ fetchToken, onAuthChange })` | Set auth with automatic token refresh, returns `AuthHandleWrapper` | +| `authState` | Stream of auth state changes (`Stream`) | +| `isAuthenticated` | Current auth state (sync getter) | +| `clearAuth()` | Clear auth and stop token refresh | +| `connectionState` | Real-time WebSocket connection state stream (`Stream`) | +| `currentConnectionState` | Current connection state (sync getter) | +| `isConnected` | Returns true if WebSocket is connected (sync getter) | +| `checkConnection()` | _(Deprecated)_ Manually check connection status, returns `ConnectionStatus` | +| `reconnect()` | Manually trigger reconnection attempt, returns `bool` | +| `lifecycleEvents` | Stream of app lifecycle events (`Stream`) | +| `dispose()` | Clean up client resources | + +See the inline docs in `lib/src/convex_client.dart` for details. + +## Example app + +An example is provided under `example/`: + +``` +cd example +flutter run +``` + +The example demonstrates: +- Real-time chat with subscriptions +- Sending messages with mutations +- Authentication with JWT tokens +- Auth state management +- **WebSocket connection state monitoring** with visual indicators +- Lifecycle event monitoring (shows app state in AppBar) +- Connection screen with real-time state history +- Automatic connection on app startup +- Singleton pattern usage (`ConvexClient.instance`) + +## Troubleshooting + +### Build Issues + +- **Rust not found** (native platforms only): + - Visit [Rust Getting Started Guide](https://www.rust-lang.org/learn/get-started) + - Install Rust: + ```bash + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + ``` + - Update your PATH (add to `~/.bashrc`, `~/.zshrc`, or equivalent): + ```bash + source "$HOME/.cargo/env" + ``` + - Verify installation: + ```bash + rustc --version + cargo --version + ``` + - **Note**: Not needed for web platform +- **Android build issues**: Use JDK 11, ensure NDK is installed via Android SDK Manager +- **iOS/macOS**: Run `pod install` inside the `example/ios` or your app's `ios` folder if needed +- **Windows**: Install Visual Studio Build Tools with C++ workload + +### Connection Issues + +- **macOS stuck in "connecting" state**: Missing network entitlements - see [PLATFORM_CONFIGURATION.md](PLATFORM_CONFIGURATION.md#macos) +- **Android network errors**: Missing INTERNET permission - see [PLATFORM_CONFIGURATION.md](PLATFORM_CONFIGURATION.md#android) +- **WebSocket not connecting**: Check your `deploymentUrl` and network permissions +- **Timeout errors**: Increase `operationTimeout` in `ConvexConfig` + +**📖 For detailed troubleshooting, see [PLATFORM_CONFIGURATION.md](PLATFORM_CONFIGURATION.md#troubleshooting)** + +## Contributing + +Contributions are welcome! Please open an issue or pull request. + +## License + +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/third_party/convex_flutter/analysis_options.yaml b/third_party/convex_flutter/analysis_options.yaml new file mode 100644 index 00000000..a5744c1c --- /dev/null +++ b/third_party/convex_flutter/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/third_party/convex_flutter/android/build.gradle b/third_party/convex_flutter/android/build.gradle new file mode 100644 index 00000000..dfbbaf3b --- /dev/null +++ b/third_party/convex_flutter/android/build.gradle @@ -0,0 +1,56 @@ +// The Android Gradle Plugin builds the native code with the Android NDK. + +group 'com.flutter_rust_bridge.convex_flutter' +version '1.0' + +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + // The Android Gradle Plugin knows how to build native code with the NDK. + classpath 'com.android.tools.build:gradle:7.3.0' + } +} + +rootProject.allprojects { + repositories { + google() + mavenCentral() + } +} + +apply plugin: 'com.android.library' + +android { + if (project.android.hasProperty("namespace")) { + namespace 'com.flutter_rust_bridge.convex_flutter' + } + + // Bumping the plugin compileSdkVersion requires all clients of this plugin + // to bump the version in their app. + compileSdkVersion 33 + + // Use the NDK version + // declared in /android/app/build.gradle file of the Flutter project. + // Replace it with a version number if this plugin requires a specfic NDK version. + // (e.g. ndkVersion "23.1.7779620") + ndkVersion android.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + minSdkVersion 19 + } +} + +apply from: "../cargokit/gradle/plugin.gradle" +cargokit { + manifestDir = "../rust" + libname = "convex_flutter" +} diff --git a/third_party/convex_flutter/android/settings.gradle b/third_party/convex_flutter/android/settings.gradle new file mode 100644 index 00000000..f6e5df69 --- /dev/null +++ b/third_party/convex_flutter/android/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'convex_flutter' diff --git a/third_party/convex_flutter/android/src/main/AndroidManifest.xml b/third_party/convex_flutter/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..43a45c47 --- /dev/null +++ b/third_party/convex_flutter/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/third_party/convex_flutter/build.yaml b/third_party/convex_flutter/build.yaml new file mode 100644 index 00000000..d82d0b6d --- /dev/null +++ b/third_party/convex_flutter/build.yaml @@ -0,0 +1,6 @@ +targets: + $default: + builders: + freezed: + generate_for: + - lib/src/rust/*.dart diff --git a/third_party/convex_flutter/cargokit/LICENSE b/third_party/convex_flutter/cargokit/LICENSE new file mode 100644 index 00000000..d33a5fea --- /dev/null +++ b/third_party/convex_flutter/cargokit/LICENSE @@ -0,0 +1,42 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Copyright 2022 Matej Knopp + +================================================================================ + +MIT LICENSE + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS +OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +================================================================================ + +APACHE LICENSE, VERSION 2.0 + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + diff --git a/third_party/convex_flutter/cargokit/README b/third_party/convex_flutter/cargokit/README new file mode 100644 index 00000000..398474db --- /dev/null +++ b/third_party/convex_flutter/cargokit/README @@ -0,0 +1,11 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +Experimental repository to provide glue for seamlessly integrating cargo build +with flutter plugins and packages. + +See https://matejknopp.com/post/flutter_plugin_in_rust_with_no_prebuilt_binaries/ +for a tutorial on how to use Cargokit. + +Example plugin available at https://github.com/irondash/hello_rust_ffi_plugin. + diff --git a/third_party/convex_flutter/cargokit/build_pod.sh b/third_party/convex_flutter/cargokit/build_pod.sh new file mode 100755 index 00000000..ed0e0d98 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_pod.sh @@ -0,0 +1,58 @@ +#!/bin/sh +set -e + +BASEDIR=$(dirname "$0") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +BASEDIR=$(cd "$BASEDIR" ; pwd -P) + +# Remove XCode SDK from path. Otherwise this breaks tool compilation when building iOS project +NEW_PATH=`echo $PATH | tr ":" "\n" | grep -v "Contents/Developer/" | tr "\n" ":"` + +export PATH=${NEW_PATH%?} # remove trailing : + +env + +# Platform name (macosx, iphoneos, iphonesimulator) +export CARGOKIT_DARWIN_PLATFORM_NAME=$PLATFORM_NAME + +# Arctive architectures (arm64, armv7, x86_64), space separated. +export CARGOKIT_DARWIN_ARCHS=$ARCHS + +# Current build configuration (Debug, Release) +export CARGOKIT_CONFIGURATION=$CONFIGURATION + +# Path to directory containing Cargo.toml. +export CARGOKIT_MANIFEST_DIR=$PODS_TARGET_SRCROOT/$1 + +# Temporary directory for build artifacts. +export CARGOKIT_TARGET_TEMP_DIR=$TARGET_TEMP_DIR + +# Output directory for final artifacts. +export CARGOKIT_OUTPUT_DIR=$PODS_CONFIGURATION_BUILD_DIR/$PRODUCT_NAME + +# Directory to store built tool artifacts. +export CARGOKIT_TOOL_TEMP_DIR=$TARGET_TEMP_DIR/build_tool + +# Directory inside root project. Not necessarily the top level directory of root project. +export CARGOKIT_ROOT_PROJECT_DIR=$SRCROOT + +FLUTTER_EXPORT_BUILD_ENVIRONMENT=( + "$PODS_ROOT/../Flutter/ephemeral/flutter_export_environment.sh" # macOS + "$PODS_ROOT/../Flutter/flutter_export_environment.sh" # iOS +) + +for path in "${FLUTTER_EXPORT_BUILD_ENVIRONMENT[@]}" +do + if [[ -f "$path" ]]; then + source "$path" + fi +done + +sh "$BASEDIR/run_build_tool.sh" build-pod "$@" + +# Make a symlink from built framework to phony file, which will be used as input to +# build script. This should force rebuild (podspec currently doesn't support alwaysOutOfDate +# attribute on custom build phase) +ln -fs "$OBJROOT/XCBuildData/build.db" "${BUILT_PRODUCTS_DIR}/cargokit_phony" +ln -fs "${BUILT_PRODUCTS_DIR}/${EXECUTABLE_PATH}" "${BUILT_PRODUCTS_DIR}/cargokit_phony_out" diff --git a/third_party/convex_flutter/cargokit/build_tool/README.md b/third_party/convex_flutter/cargokit/build_tool/README.md new file mode 100644 index 00000000..a878c279 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/README.md @@ -0,0 +1,5 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +A sample command-line application with an entrypoint in `bin/`, library code +in `lib/`, and example unit test in `test/`. diff --git a/third_party/convex_flutter/cargokit/build_tool/analysis_options.yaml b/third_party/convex_flutter/cargokit/build_tool/analysis_options.yaml new file mode 100644 index 00000000..0e16a8b0 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/analysis_options.yaml @@ -0,0 +1,34 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +# This file configures the static analysis results for your project (errors, +# warnings, and lints). +# +# This enables the 'recommended' set of lints from `package:lints`. +# This set helps identify many issues that may lead to problems when running +# or consuming Dart code, and enforces writing Dart using a single, idiomatic +# style and format. +# +# If you want a smaller set of lints you can change this to specify +# 'package:lints/core.yaml'. These are just the most critical lints +# (the recommended set includes the core lints). +# The core lints are also what is used by pub.dev for scoring packages. + +include: package:lints/recommended.yaml + +# Uncomment the following section to specify additional rules. + +linter: + rules: + - prefer_relative_imports + - directives_ordering + +# analyzer: +# exclude: +# - path/to/excluded/files/** + +# For more information about the core and recommended set of lints, see +# https://dart.dev/go/core-lints + +# For additional information about configuring this file, see +# https://dart.dev/guides/language/analysis-options diff --git a/third_party/convex_flutter/cargokit/build_tool/bin/build_tool.dart b/third_party/convex_flutter/cargokit/build_tool/bin/build_tool.dart new file mode 100644 index 00000000..268eb524 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/bin/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:build_tool/build_tool.dart' as build_tool; + +void main(List arguments) { + build_tool.runMain(arguments); +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/build_tool.dart b/third_party/convex_flutter/cargokit/build_tool/lib/build_tool.dart new file mode 100644 index 00000000..7c1bb750 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/build_tool.dart @@ -0,0 +1,8 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'src/build_tool.dart' as build_tool; + +Future runMain(List args) async { + return build_tool.runMain(args); +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/android_environment.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/android_environment.dart new file mode 100644 index 00000000..15fc9eed --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/android_environment.dart @@ -0,0 +1,195 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math' as math; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; +import 'package:version/version.dart'; + +import 'target.dart'; +import 'util.dart'; + +class AndroidEnvironment { + AndroidEnvironment({ + required this.sdkPath, + required this.ndkVersion, + required this.minSdkVersion, + required this.targetTempDir, + required this.target, + }); + + static void clangLinkerWrapper(List args) { + final clang = Platform.environment['_CARGOKIT_NDK_LINK_CLANG']; + if (clang == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_CLANG env var"); + } + final target = Platform.environment['_CARGOKIT_NDK_LINK_TARGET']; + if (target == null) { + throw Exception( + "cargo-ndk rustc linker: didn't find _CARGOKIT_NDK_LINK_TARGET env var"); + } + + runCommand(clang, [ + target, + ...args, + ]); + } + + /// Full path to Android SDK. + final String sdkPath; + + /// Full version of Android NDK. + final String ndkVersion; + + /// Minimum supported SDK version. + final int minSdkVersion; + + /// Target directory for build artifacts. + final String targetTempDir; + + /// Target being built. + final Target target; + + bool ndkIsInstalled() { + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final ndkPackageXml = File(path.join(ndkPath, 'package.xml')); + return ndkPackageXml.existsSync(); + } + + void installNdk({ + required String javaHome, + }) { + final sdkManagerExtension = Platform.isWindows ? '.bat' : ''; + final sdkManager = path.join( + sdkPath, + 'cmdline-tools', + 'latest', + 'bin', + 'sdkmanager$sdkManagerExtension', + ); + + log.info('Installing NDK $ndkVersion'); + runCommand(sdkManager, [ + '--install', + 'ndk;$ndkVersion', + ], environment: { + 'JAVA_HOME': javaHome, + }); + } + + Future> buildEnvironment() async { + final hostArch = Platform.isMacOS + ? "darwin-x86_64" + : (Platform.isLinux ? "linux-x86_64" : "windows-x86_64"); + + final ndkPath = path.join(sdkPath, 'ndk', ndkVersion); + final toolchainPath = path.join( + ndkPath, + 'toolchains', + 'llvm', + 'prebuilt', + hostArch, + 'bin', + ); + + final minSdkVersion = + math.max(target.androidMinSdkVersion!, this.minSdkVersion); + + final exe = Platform.isWindows ? '.exe' : ''; + + final arKey = 'AR_${target.rust}'; + final arValue = ['${target.rust}-ar', 'llvm-ar', 'llvm-ar.exe'] + .map((e) => path.join(toolchainPath, e)) + .firstWhereOrNull((element) => File(element).existsSync()); + if (arValue == null) { + throw Exception('Failed to find ar for $target in $toolchainPath'); + } + + final targetArg = '--target=${target.rust}$minSdkVersion'; + + final ccKey = 'CC_${target.rust}'; + final ccValue = path.join(toolchainPath, 'clang$exe'); + final cfFlagsKey = 'CFLAGS_${target.rust}'; + final cFlagsValue = targetArg; + + final cxxKey = 'CXX_${target.rust}'; + final cxxValue = path.join(toolchainPath, 'clang++$exe'); + final cxxFlagsKey = 'CXXFLAGS_${target.rust}'; + final cxxFlagsValue = targetArg; + + final linkerKey = + 'cargo_target_${target.rust.replaceAll('-', '_')}_linker'.toUpperCase(); + + final ranlibKey = 'RANLIB_${target.rust}'; + final ranlibValue = path.join(toolchainPath, 'llvm-ranlib$exe'); + + final ndkVersionParsed = Version.parse(ndkVersion); + final rustFlagsKey = 'CARGO_ENCODED_RUSTFLAGS'; + final rustFlagsValue = _libGccWorkaround(targetTempDir, ndkVersionParsed); + + final runRustTool = + Platform.isWindows ? 'run_build_tool.cmd' : 'run_build_tool.sh'; + + final packagePath = (await Isolate.resolvePackageUri( + Uri.parse('package:build_tool/buildtool.dart')))! + .toFilePath(); + final selfPath = path.canonicalize(path.join( + packagePath, + '..', + '..', + '..', + runRustTool, + )); + + // Make sure that run_build_tool is working properly even initially launched directly + // through dart run. + final toolTempDir = + Platform.environment['CARGOKIT_TOOL_TEMP_DIR'] ?? targetTempDir; + + return { + arKey: arValue, + ccKey: ccValue, + cfFlagsKey: cFlagsValue, + cxxKey: cxxValue, + cxxFlagsKey: cxxFlagsValue, + ranlibKey: ranlibValue, + rustFlagsKey: rustFlagsValue, + linkerKey: selfPath, + // Recognized by main() so we know when we're acting as a wrapper + '_CARGOKIT_NDK_LINK_TARGET': targetArg, + '_CARGOKIT_NDK_LINK_CLANG': ccValue, + 'CARGOKIT_TOOL_TEMP_DIR': toolTempDir, + }; + } + + // Workaround for libgcc missing in NDK23, inspired by cargo-ndk + String _libGccWorkaround(String buildDir, Version ndkVersion) { + final workaroundDir = path.join( + buildDir, + 'cargokit', + 'libgcc_workaround', + '${ndkVersion.major}', + ); + Directory(workaroundDir).createSync(recursive: true); + if (ndkVersion.major >= 23) { + File(path.join(workaroundDir, 'libgcc.a')) + .writeAsStringSync('INPUT(-lunwind)'); + } else { + // Other way around, untested, forward libgcc.a from libunwind once Rust + // gets updated for NDK23+. + File(path.join(workaroundDir, 'libunwind.a')) + .writeAsStringSync('INPUT(-lgcc)'); + } + + var rustFlags = Platform.environment['CARGO_ENCODED_RUSTFLAGS'] ?? ''; + if (rustFlags.isNotEmpty) { + rustFlags = '$rustFlags\x1f'; + } + rustFlags = '$rustFlags-L\x1f$workaroundDir'; + return rustFlags; + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/artifacts_provider.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/artifacts_provider.dart new file mode 100644 index 00000000..e608cece --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/artifacts_provider.dart @@ -0,0 +1,266 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'builder.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'rustup.dart'; +import 'target.dart'; + +class Artifact { + /// File system location of the artifact. + final String path; + + /// Actual file name that the artifact should have in destination folder. + final String finalFileName; + + AritifactType get type { + if (finalFileName.endsWith('.dll') || + finalFileName.endsWith('.dll.lib') || + finalFileName.endsWith('.pdb') || + finalFileName.endsWith('.so') || + finalFileName.endsWith('.dylib')) { + return AritifactType.dylib; + } else if (finalFileName.endsWith('.lib') || finalFileName.endsWith('.a')) { + return AritifactType.staticlib; + } else { + throw Exception('Unknown artifact type for $finalFileName'); + } + } + + Artifact({ + required this.path, + required this.finalFileName, + }); +} + +final _log = Logger('artifacts_provider'); + +class ArtifactProvider { + ArtifactProvider({ + required this.environment, + required this.userOptions, + }); + + final BuildEnvironment environment; + final CargokitUserOptions userOptions; + + Future>> getArtifacts(List targets) async { + final result = await _getPrecompiledArtifacts(targets); + + final pendingTargets = List.of(targets); + pendingTargets.removeWhere((element) => result.containsKey(element)); + + if (pendingTargets.isEmpty) { + return result; + } + + final rustup = Rustup(); + for (final target in targets) { + final builder = RustBuilder(target: target, environment: environment); + builder.prepare(rustup); + _log.info('Building ${environment.crateInfo.packageName} for $target'); + final targetDir = await builder.build(); + // For local build accept both static and dynamic libraries. + final artifactNames = { + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.dylib, + remote: false, + ), + ...getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + aritifactType: AritifactType.staticlib, + remote: false, + ) + }; + final artifacts = artifactNames + .map((artifactName) => Artifact( + path: path.join(targetDir, artifactName), + finalFileName: artifactName, + )) + .where((element) => File(element.path).existsSync()) + .toList(); + result[target] = artifacts; + } + return result; + } + + Future>> _getPrecompiledArtifacts( + List targets) async { + if (userOptions.usePrecompiledBinaries == false) { + _log.info('Precompiled binaries are disabled'); + return {}; + } + if (environment.crateOptions.precompiledBinaries == null) { + _log.fine('Precompiled binaries not enabled for this crate'); + return {}; + } + + final start = Stopwatch()..start(); + final crateHash = CrateHash.compute(environment.manifestDir, + tempStorage: environment.targetTempDir); + _log.fine( + 'Computed crate hash $crateHash in ${start.elapsedMilliseconds}ms'); + + final downloadedArtifactsDir = + path.join(environment.targetTempDir, 'precompiled', crateHash); + Directory(downloadedArtifactsDir).createSync(recursive: true); + + final res = >{}; + + for (final target in targets) { + final requiredArtifacts = getArtifactNames( + target: target, + libraryName: environment.crateInfo.packageName, + remote: true, + ); + final artifactsForTarget = []; + + for (final artifact in requiredArtifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final downloadedPath = path.join(downloadedArtifactsDir, fileName); + if (!File(downloadedPath).existsSync()) { + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + await _tryDownloadArtifacts( + crateHash: crateHash, + fileName: fileName, + signatureFileName: signatureFileName, + finalPath: downloadedPath, + ); + } + if (File(downloadedPath).existsSync()) { + artifactsForTarget.add(Artifact( + path: downloadedPath, + finalFileName: artifact, + )); + } else { + break; + } + } + + // Only provide complete set of artifacts. + if (artifactsForTarget.length == requiredArtifacts.length) { + _log.fine('Found precompiled artifacts for $target'); + res[target] = artifactsForTarget; + } + } + + return res; + } + + static Future _get(Uri url, {Map? headers}) async { + int attempt = 0; + const maxAttempts = 10; + while (true) { + try { + return await get(url, headers: headers); + } on SocketException catch (e) { + // Try to detect reset by peer error and retry. + if (attempt++ < maxAttempts && + (e.osError?.errorCode == 54 || e.osError?.errorCode == 10054)) { + _log.severe( + 'Failed to download $url: $e, attempt $attempt of $maxAttempts, will retry...'); + await Future.delayed(Duration(seconds: 1)); + continue; + } else { + rethrow; + } + } + } + } + + Future _tryDownloadArtifacts({ + required String crateHash, + required String fileName, + required String signatureFileName, + required String finalPath, + }) async { + final precompiledBinaries = environment.crateOptions.precompiledBinaries!; + final prefix = precompiledBinaries.uriPrefix; + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = Uri.parse('$prefix$crateHash/$signatureFileName'); + _log.fine('Downloading signature from $signatureUrl'); + final signature = await _get(signatureUrl); + if (signature.statusCode == 404) { + _log.warning( + 'Precompiled binaries not available for crate hash $crateHash ($fileName)'); + return; + } + if (signature.statusCode != 200) { + _log.severe( + 'Failed to download signature $signatureUrl: status ${signature.statusCode}'); + return; + } + _log.fine('Downloading binary from $url'); + final res = await _get(url); + if (res.statusCode != 200) { + _log.severe('Failed to download binary $url: status ${res.statusCode}'); + return; + } + if (verify( + precompiledBinaries.publicKey, res.bodyBytes, signature.bodyBytes)) { + File(finalPath).writeAsBytesSync(res.bodyBytes); + } else { + _log.shout('Signature verification failed! Ignoring binary.'); + } + } +} + +enum AritifactType { + staticlib, + dylib, +} + +AritifactType artifactTypeForTarget(Target target) { + if (target.darwinPlatform != null) { + return AritifactType.staticlib; + } else { + return AritifactType.dylib; + } +} + +List getArtifactNames({ + required Target target, + required String libraryName, + required bool remote, + AritifactType? aritifactType, +}) { + aritifactType ??= artifactTypeForTarget(target); + if (target.darwinArch != null) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.dylib']; + } + } else if (target.rust.contains('-windows-')) { + if (aritifactType == AritifactType.staticlib) { + return ['$libraryName.lib']; + } else { + return [ + '$libraryName.dll', + '$libraryName.dll.lib', + if (!remote) '$libraryName.pdb' + ]; + } + } else if (target.rust.contains('-linux-')) { + if (aritifactType == AritifactType.staticlib) { + return ['lib$libraryName.a']; + } else { + return ['lib$libraryName.so']; + } + } else { + throw Exception("Unsupported target: ${target.rust}"); + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/build_cmake.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_cmake.dart new file mode 100644 index 00000000..6f3b2a4e --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_cmake.dart @@ -0,0 +1,40 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +class BuildCMake { + final CargokitUserOptions userOptions; + + BuildCMake({required this.userOptions}); + + Future build() async { + final targetPlatform = Environment.targetPlatform; + final target = Target.forFlutterName(Environment.targetPlatform); + if (target == null) { + throw Exception("Unknown target platform: $targetPlatform"); + } + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts([target]); + + final libs = artifacts[target]!; + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path) + .copySync(path.join(Environment.outputDir, lib.finalFileName)); + } + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/build_gradle.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_gradle.dart new file mode 100644 index 00000000..7e61fcbb --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_gradle.dart @@ -0,0 +1,49 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; + +final log = Logger('build_gradle'); + +class BuildGradle { + BuildGradle({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.targetPlatforms.map((arch) { + final target = Target.forFlutterName(arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: true); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + for (final target in targets) { + final libs = artifacts[target]!; + final outputDir = path.join(Environment.outputDir, target.android!); + Directory(outputDir).createSync(recursive: true); + + for (final lib in libs) { + if (lib.type == AritifactType.dylib) { + File(lib.path).copySync(path.join(outputDir, lib.finalFileName)); + } + } + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/build_pod.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_pod.dart new file mode 100644 index 00000000..8a9c0db5 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_pod.dart @@ -0,0 +1,89 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'target.dart'; +import 'util.dart'; + +class BuildPod { + BuildPod({required this.userOptions}); + + final CargokitUserOptions userOptions; + + Future build() async { + final targets = Environment.darwinArchs.map((arch) { + final target = Target.forDarwin( + platformName: Environment.darwinPlatformName, darwinAarch: arch); + if (target == null) { + throw Exception( + "Unknown darwin target or platform: $arch, ${Environment.darwinPlatformName}"); + } + return target; + }).toList(); + + final environment = BuildEnvironment.fromEnvironment(isAndroid: false); + final provider = + ArtifactProvider(environment: environment, userOptions: userOptions); + final artifacts = await provider.getArtifacts(targets); + + void performLipo(String targetFile, Iterable sourceFiles) { + runCommand("lipo", [ + '-create', + ...sourceFiles, + '-output', + targetFile, + ]); + } + + final outputDir = Environment.outputDir; + + Directory(outputDir).createSync(recursive: true); + + final staticLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.staticlib) + .toList(); + final dynamicLibs = artifacts.values + .expand((element) => element) + .where((element) => element.type == AritifactType.dylib) + .toList(); + + final libName = environment.crateInfo.packageName; + + // If there is static lib, use it and link it with pod + if (staticLibs.isNotEmpty) { + final finalTargetFile = path.join(outputDir, "lib$libName.a"); + performLipo(finalTargetFile, staticLibs.map((e) => e.path)); + } else { + // Otherwise try to replace bundle dylib with our dylib + final bundlePaths = [ + '$libName.framework/Versions/A/$libName', + '$libName.framework/$libName', + ]; + + for (final bundlePath in bundlePaths) { + final targetFile = path.join(outputDir, bundlePath); + if (File(targetFile).existsSync()) { + performLipo(targetFile, dynamicLibs.map((e) => e.path)); + + // Replace absolute id with @rpath one so that it works properly + // when moved to Frameworks. + runCommand("install_name_tool", [ + '-id', + '@rpath/$bundlePath', + targetFile, + ]); + return; + } + } + throw Exception('Unable to find bundle for dynamic library'); + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/build_tool.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_tool.dart new file mode 100644 index 00000000..c8f36981 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/build_tool.dart @@ -0,0 +1,271 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; + +import 'android_environment.dart'; +import 'build_cmake.dart'; +import 'build_gradle.dart'; +import 'build_pod.dart'; +import 'logging.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; +import 'util.dart'; +import 'verify_binaries.dart'; + +final log = Logger('build_tool'); + +abstract class BuildCommand extends Command { + Future runBuildCommand(CargokitUserOptions options); + + @override + Future run() async { + final options = CargokitUserOptions.load(); + + if (options.verboseLogging || + Platform.environment['CARGOKIT_VERBOSE'] == '1') { + enableVerboseLogging(); + } + + await runBuildCommand(options); + } +} + +class BuildPodCommand extends BuildCommand { + @override + final name = 'build-pod'; + + @override + final description = 'Build cocoa pod library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildPod(userOptions: options); + await build.build(); + } +} + +class BuildGradleCommand extends BuildCommand { + @override + final name = 'build-gradle'; + + @override + final description = 'Build android library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildGradle(userOptions: options); + await build.build(); + } +} + +class BuildCMakeCommand extends BuildCommand { + @override + final name = 'build-cmake'; + + @override + final description = 'Build CMake library'; + + @override + Future runBuildCommand(CargokitUserOptions options) async { + final build = BuildCMake(userOptions: options); + await build.build(); + } +} + +class GenKeyCommand extends Command { + @override + final name = 'gen-key'; + + @override + final description = 'Generate key pair for signing precompiled binaries'; + + @override + void run() { + final kp = generateKey(); + final private = HEX.encode(kp.privateKey.bytes); + final public = HEX.encode(kp.publicKey.bytes); + print("Private Key: $private"); + print("Public Key: $public"); + } +} + +class PrecompileBinariesCommand extends Command { + PrecompileBinariesCommand() { + argParser + ..addOption( + 'repository', + mandatory: true, + help: 'Github repository slug in format owner/name', + ) + ..addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ) + ..addMultiOption('target', + help: 'Rust target triple of artifact to build.\n' + 'Can be specified multiple times or omitted in which case\n' + 'all targets for current platform will be built.') + ..addOption( + 'android-sdk-location', + help: 'Location of Android SDK (if available)', + ) + ..addOption( + 'android-ndk-version', + help: 'Android NDK version (if available)', + ) + ..addOption( + 'android-min-sdk-version', + help: 'Android minimum rquired version (if available)', + ) + ..addOption( + 'temp-dir', + help: 'Directory to store temporary build artifacts', + ) + ..addFlag( + "verbose", + abbr: "v", + defaultsTo: false, + help: "Enable verbose logging", + ); + } + + @override + final name = 'precompile-binaries'; + + @override + final description = 'Prebuild and upload binaries\n' + 'Private key must be passed through PRIVATE_KEY environment variable. ' + 'Use gen_key through generate priave key.\n' + 'Github token must be passed as GITHUB_TOKEN environment variable.\n'; + + @override + Future run() async { + final verbose = argResults!['verbose'] as bool; + if (verbose) { + enableVerboseLogging(); + } + + final privateKeyString = Platform.environment['PRIVATE_KEY']; + if (privateKeyString == null) { + throw ArgumentError('Missing PRIVATE_KEY environment variable'); + } + final githubToken = Platform.environment['GITHUB_TOKEN']; + if (githubToken == null) { + throw ArgumentError('Missing GITHUB_TOKEN environment variable'); + } + final privateKey = HEX.decode(privateKeyString); + if (privateKey.length != 64) { + throw ArgumentError('Private key must be 64 bytes long'); + } + final manifestDir = argResults!['manifest-dir'] as String; + if (!Directory(manifestDir).existsSync()) { + throw ArgumentError('Manifest directory does not exist: $manifestDir'); + } + String? androidMinSdkVersionString = + argResults!['android-min-sdk-version'] as String?; + int? androidMinSdkVersion; + if (androidMinSdkVersionString != null) { + androidMinSdkVersion = int.tryParse(androidMinSdkVersionString); + if (androidMinSdkVersion == null) { + throw ArgumentError( + 'Invalid android-min-sdk-version: $androidMinSdkVersionString'); + } + } + final targetStrigns = argResults!['target'] as List; + final targets = targetStrigns.map((target) { + final res = Target.forRustTriple(target); + if (res == null) { + throw ArgumentError('Invalid target: $target'); + } + return res; + }).toList(growable: false); + final precompileBinaries = PrecompileBinaries( + privateKey: PrivateKey(privateKey), + githubToken: githubToken, + manifestDir: manifestDir, + repositorySlug: RepositorySlug.full(argResults!['repository'] as String), + targets: targets, + androidSdkLocation: argResults!['android-sdk-location'] as String?, + androidNdkVersion: argResults!['android-ndk-version'] as String?, + androidMinSdkVersion: androidMinSdkVersion, + tempDir: argResults!['temp-dir'] as String?, + ); + + await precompileBinaries.run(); + } +} + +class VerifyBinariesCommand extends Command { + VerifyBinariesCommand() { + argParser.addOption( + 'manifest-dir', + mandatory: true, + help: 'Directory containing Cargo.toml', + ); + } + + @override + final name = "verify-binaries"; + + @override + final description = 'Verifies published binaries\n' + 'Checks whether there is a binary published for each targets\n' + 'and checks the signature.'; + + @override + Future run() async { + final manifestDir = argResults!['manifest-dir'] as String; + final verifyBinaries = VerifyBinaries( + manifestDir: manifestDir, + ); + await verifyBinaries.run(); + } +} + +Future runMain(List args) async { + try { + // Init logging before options are loaded + initLogging(); + + if (Platform.environment['_CARGOKIT_NDK_LINK_TARGET'] != null) { + return AndroidEnvironment.clangLinkerWrapper(args); + } + + final runner = CommandRunner('build_tool', 'Cargokit built_tool') + ..addCommand(BuildPodCommand()) + ..addCommand(BuildGradleCommand()) + ..addCommand(BuildCMakeCommand()) + ..addCommand(GenKeyCommand()) + ..addCommand(PrecompileBinariesCommand()) + ..addCommand(VerifyBinariesCommand()); + + await runner.run(args); + } on ArgumentError catch (e) { + stderr.writeln(e.toString()); + exit(1); + } catch (e, s) { + log.severe(kDoubleSeparator); + log.severe('Cargokit BuildTool failed with error:'); + log.severe(kSeparator); + log.severe(e); + // This tells user to install Rust, there's no need to pollute the log with + // stack trace. + if (e is! RustupNotFoundException) { + log.severe(kSeparator); + log.severe(s); + log.severe(kSeparator); + log.severe('BuildTool arguments: $args'); + } + log.severe(kDoubleSeparator); + exit(1); + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/builder.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/builder.dart new file mode 100644 index 00000000..84c46e4f --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/builder.dart @@ -0,0 +1,198 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'android_environment.dart'; +import 'cargo.dart'; +import 'environment.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; +import 'util.dart'; + +final _log = Logger('builder'); + +enum BuildConfiguration { + debug, + release, + profile, +} + +extension on BuildConfiguration { + bool get isDebug => this == BuildConfiguration.debug; + String get rustName => switch (this) { + BuildConfiguration.debug => 'debug', + BuildConfiguration.release => 'release', + BuildConfiguration.profile => 'release', + }; +} + +class BuildException implements Exception { + final String message; + + BuildException(this.message); + + @override + String toString() { + return 'BuildException: $message'; + } +} + +class BuildEnvironment { + final BuildConfiguration configuration; + final CargokitCrateOptions crateOptions; + final String targetTempDir; + final String manifestDir; + final CrateInfo crateInfo; + + final bool isAndroid; + final String? androidSdkPath; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? javaHome; + + BuildEnvironment({ + required this.configuration, + required this.crateOptions, + required this.targetTempDir, + required this.manifestDir, + required this.crateInfo, + required this.isAndroid, + this.androidSdkPath, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.javaHome, + }); + + static BuildConfiguration parseBuildConfiguration(String value) { + // XCode configuration adds the flavor to configuration name. + final firstSegment = value.split('-').first; + final buildConfiguration = BuildConfiguration.values.firstWhereOrNull( + (e) => e.name == firstSegment, + ); + if (buildConfiguration == null) { + _log.warning('Unknown build configuraiton $value, will assume release'); + return BuildConfiguration.release; + } + return buildConfiguration; + } + + static BuildEnvironment fromEnvironment({ + required bool isAndroid, + }) { + final buildConfiguration = + parseBuildConfiguration(Environment.configuration); + final manifestDir = Environment.manifestDir; + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + final crateInfo = CrateInfo.load(manifestDir); + return BuildEnvironment( + configuration: buildConfiguration, + crateOptions: crateOptions, + targetTempDir: Environment.targetTempDir, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: isAndroid, + androidSdkPath: isAndroid ? Environment.sdkPath : null, + androidNdkVersion: isAndroid ? Environment.ndkVersion : null, + androidMinSdkVersion: + isAndroid ? int.parse(Environment.minSdkVersion) : null, + javaHome: isAndroid ? Environment.javaHome : null, + ); + } +} + +class RustBuilder { + final Target target; + final BuildEnvironment environment; + + RustBuilder({ + required this.target, + required this.environment, + }); + + void prepare( + Rustup rustup, + ) { + final toolchain = _toolchain; + if (rustup.installedTargets(toolchain) == null) { + rustup.installToolchain(toolchain); + } + if (toolchain == 'nightly') { + rustup.installRustSrcForNightly(); + } + if (!rustup.installedTargets(toolchain)!.contains(target.rust)) { + rustup.installTarget(target.rust, toolchain: toolchain); + } + } + + CargoBuildOptions? get _buildOptions => + environment.crateOptions.cargo[environment.configuration]; + + String get _toolchain => _buildOptions?.toolchain.name ?? 'stable'; + + /// Returns the path of directory containing build artifacts. + Future build() async { + final extraArgs = _buildOptions?.flags ?? []; + final manifestPath = path.join(environment.manifestDir, 'Cargo.toml'); + runCommand( + 'rustup', + [ + 'run', + _toolchain, + 'cargo', + 'build', + ...extraArgs, + '--manifest-path', + manifestPath, + '-p', + environment.crateInfo.packageName, + if (!environment.configuration.isDebug) '--release', + '--target', + target.rust, + '--target-dir', + environment.targetTempDir, + ], + environment: await _buildEnvironment(), + ); + return path.join( + environment.targetTempDir, + target.rust, + environment.configuration.rustName, + ); + } + + Future> _buildEnvironment() async { + if (target.android == null) { + return {}; + } else { + final sdkPath = environment.androidSdkPath; + final ndkVersion = environment.androidNdkVersion; + final minSdkVersion = environment.androidMinSdkVersion; + if (sdkPath == null) { + throw BuildException('androidSdkPath is not set'); + } + if (ndkVersion == null) { + throw BuildException('androidNdkVersion is not set'); + } + if (minSdkVersion == null) { + throw BuildException('androidMinSdkVersion is not set'); + } + final env = AndroidEnvironment( + sdkPath: sdkPath, + ndkVersion: ndkVersion, + minSdkVersion: minSdkVersion, + targetTempDir: environment.targetTempDir, + target: target, + ); + if (!env.ndkIsInstalled() && environment.javaHome != null) { + env.installNdk(javaHome: environment.javaHome!); + } + return env.buildEnvironment(); + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/cargo.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/cargo.dart new file mode 100644 index 00000000..0d8958ff --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/cargo.dart @@ -0,0 +1,48 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:path/path.dart' as path; +import 'package:toml/toml.dart'; + +class ManifestException { + ManifestException(this.message, {required this.fileName}); + + final String? fileName; + final String message; + + @override + String toString() { + if (fileName != null) { + return 'Failed to parse package manifest at $fileName: $message'; + } else { + return 'Failed to parse package manifest: $message'; + } + } +} + +class CrateInfo { + CrateInfo({required this.packageName}); + + final String packageName; + + static CrateInfo parseManifest(String manifest, {final String? fileName}) { + final toml = TomlDocument.parse(manifest); + final package = toml.toMap()['package']; + if (package == null) { + throw ManifestException('Missing package section', fileName: fileName); + } + final name = package['name']; + if (name == null) { + throw ManifestException('Missing package name', fileName: fileName); + } + return CrateInfo(packageName: name); + } + + static CrateInfo load(String manifestDir) { + final manifestFile = File(path.join(manifestDir, 'Cargo.toml')); + final manifest = manifestFile.readAsStringSync(); + return parseManifest(manifest, fileName: manifestFile.path); + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/crate_hash.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/crate_hash.dart new file mode 100644 index 00000000..0c4d88d1 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/crate_hash.dart @@ -0,0 +1,124 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:collection/collection.dart'; +import 'package:convert/convert.dart'; +import 'package:crypto/crypto.dart'; +import 'package:path/path.dart' as path; + +class CrateHash { + /// Computes a hash uniquely identifying crate content. This takes into account + /// content all all .rs files inside the src directory, as well as Cargo.toml, + /// Cargo.lock, build.rs and cargokit.yaml. + /// + /// If [tempStorage] is provided, computed hash is stored in a file in that directory + /// and reused on subsequent calls if the crate content hasn't changed. + static String compute(String manifestDir, {String? tempStorage}) { + return CrateHash._( + manifestDir: manifestDir, + tempStorage: tempStorage, + )._compute(); + } + + CrateHash._({ + required this.manifestDir, + required this.tempStorage, + }); + + String _compute() { + final files = getFiles(); + final tempStorage = this.tempStorage; + if (tempStorage != null) { + final quickHash = _computeQuickHash(files); + final quickHashFolder = Directory(path.join(tempStorage, 'crate_hash')); + quickHashFolder.createSync(recursive: true); + final quickHashFile = File(path.join(quickHashFolder.path, quickHash)); + if (quickHashFile.existsSync()) { + return quickHashFile.readAsStringSync(); + } + final hash = _computeHash(files); + quickHashFile.writeAsStringSync(hash); + return hash; + } else { + return _computeHash(files); + } + } + + /// Computes a quick hash based on files stat (without reading contents). This + /// is used to cache the real hash, which is slower to compute since it involves + /// reading every single file. + String _computeQuickHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + final data = ByteData(8); + for (final file in files) { + input.add(utf8.encode(file.path)); + final stat = file.statSync(); + data.setUint64(0, stat.size); + input.add(data.buffer.asUint8List()); + data.setUint64(0, stat.modified.millisecondsSinceEpoch); + input.add(data.buffer.asUint8List()); + } + + input.close(); + return base64Url.encode(output.events.single.bytes); + } + + String _computeHash(List files) { + final output = AccumulatorSink(); + final input = sha256.startChunkedConversion(output); + + void addTextFile(File file) { + // text Files are hashed by lines in case we're dealing with github checkout + // that auto-converts line endings. + final splitter = LineSplitter(); + if (file.existsSync()) { + final data = file.readAsStringSync(); + final lines = splitter.convert(data); + for (final line in lines) { + input.add(utf8.encode(line)); + } + } + } + + for (final file in files) { + addTextFile(file); + } + + input.close(); + final res = output.events.single; + + // Truncate to 128bits. + final hash = res.bytes.sublist(0, 16); + return hex.encode(hash); + } + + List getFiles() { + final src = Directory(path.join(manifestDir, 'src')); + final files = src + .listSync(recursive: true, followLinks: false) + .whereType() + .toList(); + files.sortBy((element) => element.path); + void addFile(String relative) { + final file = File(path.join(manifestDir, relative)); + if (file.existsSync()) { + files.add(file); + } + } + + addFile('Cargo.toml'); + addFile('Cargo.lock'); + addFile('build.rs'); + addFile('cargokit.yaml'); + return files; + } + + final String manifestDir; + final String? tempStorage; +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/environment.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/environment.dart new file mode 100644 index 00000000..996483a1 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/environment.dart @@ -0,0 +1,68 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +extension on String { + String resolveSymlink() => File(this).resolveSymbolicLinksSync(); +} + +class Environment { + /// Current build configuration (debug or release). + static String get configuration => + _getEnv("CARGOKIT_CONFIGURATION").toLowerCase(); + + static bool get isDebug => configuration == 'debug'; + static bool get isRelease => configuration == 'release'; + + /// Temporary directory where Rust build artifacts are placed. + static String get targetTempDir => _getEnv("CARGOKIT_TARGET_TEMP_DIR"); + + /// Final output directory where the build artifacts are placed. + static String get outputDir => _getEnvPath('CARGOKIT_OUTPUT_DIR'); + + /// Path to the crate manifest (containing Cargo.toml). + static String get manifestDir => _getEnvPath('CARGOKIT_MANIFEST_DIR'); + + /// Directory inside root project. Not necessarily root folder. Symlinks are + /// not resolved on purpose. + static String get rootProjectDir => _getEnv('CARGOKIT_ROOT_PROJECT_DIR'); + + // Pod + + /// Platform name (macosx, iphoneos, iphonesimulator). + static String get darwinPlatformName => + _getEnv("CARGOKIT_DARWIN_PLATFORM_NAME"); + + /// List of architectures to build for (arm64, armv7, x86_64). + static List get darwinArchs => + _getEnv("CARGOKIT_DARWIN_ARCHS").split(' '); + + // Gradle + static String get minSdkVersion => _getEnv("CARGOKIT_MIN_SDK_VERSION"); + static String get ndkVersion => _getEnv("CARGOKIT_NDK_VERSION"); + static String get sdkPath => _getEnvPath("CARGOKIT_SDK_DIR"); + static String get javaHome => _getEnvPath("CARGOKIT_JAVA_HOME"); + static List get targetPlatforms => + _getEnv("CARGOKIT_TARGET_PLATFORMS").split(','); + + // CMAKE + static String get targetPlatform => _getEnv("CARGOKIT_TARGET_PLATFORM"); + + static String _getEnv(String key) { + final res = Platform.environment[key]; + if (res == null) { + throw Exception("Missing environment variable $key"); + } + return res; + } + + static String _getEnvPath(String key) { + final res = _getEnv(key); + if (Directory(res).existsSync()) { + return res.resolveSymlink(); + } else { + return res; + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/logging.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/logging.dart new file mode 100644 index 00000000..5edd4fd1 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/logging.dart @@ -0,0 +1,52 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:logging/logging.dart'; + +const String kSeparator = "--"; +const String kDoubleSeparator = "=="; + +bool _lastMessageWasSeparator = false; + +void _log(LogRecord rec) { + final prefix = '${rec.level.name}: '; + final out = rec.level == Level.SEVERE ? stderr : stdout; + if (rec.message == kSeparator) { + if (!_lastMessageWasSeparator) { + out.write(prefix); + out.writeln('-' * 80); + _lastMessageWasSeparator = true; + } + return; + } else if (rec.message == kDoubleSeparator) { + out.write(prefix); + out.writeln('=' * 80); + _lastMessageWasSeparator = true; + return; + } + out.write(prefix); + out.writeln(rec.message); + _lastMessageWasSeparator = false; +} + +void initLogging() { + Logger.root.level = Level.INFO; + Logger.root.onRecord.listen((LogRecord rec) { + final lines = rec.message.split('\n'); + for (final line in lines) { + if (line.isNotEmpty || lines.length == 1 || line != lines.last) { + _log(LogRecord( + rec.level, + line, + rec.loggerName, + )); + } + } + }); +} + +void enableVerboseLogging() { + Logger.root.level = Level.ALL; +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/options.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/options.dart new file mode 100644 index 00000000..22aef1d3 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/options.dart @@ -0,0 +1,309 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:hex/hex.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; +import 'package:source_span/source_span.dart'; +import 'package:yaml/yaml.dart'; + +import 'builder.dart'; +import 'environment.dart'; +import 'rustup.dart'; + +final _log = Logger('options'); + +/// A class for exceptions that have source span information attached. +class SourceSpanException implements Exception { + // This is a getter so that subclasses can override it. + /// A message describing the exception. + String get message => _message; + final String _message; + + // This is a getter so that subclasses can override it. + /// The span associated with this exception. + /// + /// This may be `null` if the source location can't be determined. + SourceSpan? get span => _span; + final SourceSpan? _span; + + SourceSpanException(this._message, this._span); + + /// Returns a string representation of `this`. + /// + /// [color] may either be a [String], a [bool], or `null`. If it's a string, + /// it indicates an ANSI terminal color escape that should be used to + /// highlight the span's text. If it's `true`, it indicates that the text + /// should be highlighted using the default color. If it's `false` or `null`, + /// it indicates that the text shouldn't be highlighted. + @override + String toString({Object? color}) { + if (span == null) return message; + return 'Error on ${span!.message(message, color: color)}'; + } +} + +enum Toolchain { + stable, + beta, + nightly, +} + +class CargoBuildOptions { + final Toolchain toolchain; + final List flags; + + CargoBuildOptions({ + required this.toolchain, + required this.flags, + }); + + static Toolchain _toolchainFromNode(YamlNode node) { + if (node case YamlScalar(value: String name)) { + final toolchain = + Toolchain.values.firstWhereOrNull((element) => element.name == name); + if (toolchain != null) { + return toolchain; + } + } + throw SourceSpanException( + 'Unknown toolchain. Must be one of ${Toolchain.values.map((e) => e.name)}.', + node.span); + } + + static CargoBuildOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + Toolchain toolchain = Toolchain.stable; + List flags = []; + for (final MapEntry(:key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: 'toolchain')) { + toolchain = _toolchainFromNode(value); + } else if (key case YamlScalar(value: 'extra_flags')) { + if (value case YamlList(nodes: List list)) { + if (list.every((element) { + if (element case YamlScalar(value: String _)) { + return true; + } + return false; + })) { + flags = list.map((e) => e.value as String).toList(); + continue; + } + } + throw SourceSpanException( + 'Extra flags must be a list of strings', value.span); + } else { + throw SourceSpanException( + 'Unknown cargo option type. Must be "toolchain" or "extra_flags".', + key.span); + } + } + return CargoBuildOptions(toolchain: toolchain, flags: flags); + } +} + +extension on YamlMap { + /// Map that extracts keys so that we can do map case check on them. + Map get valueMap => + nodes.map((key, value) => MapEntry(key.value, value)); +} + +class PrecompiledBinaries { + final String uriPrefix; + final PublicKey publicKey; + + PrecompiledBinaries({ + required this.uriPrefix, + required this.publicKey, + }); + + static PublicKey _publicKeyFromHex(String key, SourceSpan? span) { + final bytes = HEX.decode(key); + if (bytes.length != 32) { + throw SourceSpanException( + 'Invalid public key. Must be 32 bytes long.', span); + } + return PublicKey(bytes); + } + + static PrecompiledBinaries parse(YamlNode node) { + if (node case YamlMap(valueMap: Map map)) { + if (map + case { + 'url_prefix': YamlNode urlPrefixNode, + 'public_key': YamlNode publicKeyNode, + }) { + final urlPrefix = switch (urlPrefixNode) { + YamlScalar(value: String urlPrefix) => urlPrefix, + _ => throw SourceSpanException( + 'Invalid URL prefix value.', urlPrefixNode.span), + }; + final publicKey = switch (publicKeyNode) { + YamlScalar(value: String publicKey) => + _publicKeyFromHex(publicKey, publicKeyNode.span), + _ => throw SourceSpanException( + 'Invalid public key value.', publicKeyNode.span), + }; + return PrecompiledBinaries( + uriPrefix: urlPrefix, + publicKey: publicKey, + ); + } + } + throw SourceSpanException( + 'Invalid precompiled binaries value. ' + 'Expected Map with "url_prefix" and "public_key".', + node.span); + } +} + +/// Cargokit options specified for Rust crate. +class CargokitCrateOptions { + CargokitCrateOptions({ + this.cargo = const {}, + this.precompiledBinaries, + }); + + final Map cargo; + final PrecompiledBinaries? precompiledBinaries; + + static CargokitCrateOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + final options = {}; + PrecompiledBinaries? precompiledBinaries; + + for (final entry in node.nodes.entries) { + if (entry + case MapEntry( + key: YamlScalar(value: 'cargo'), + value: YamlNode node, + )) { + if (node is! YamlMap) { + throw SourceSpanException('Cargo options must be a map', node.span); + } + for (final MapEntry(:YamlNode key, :value) in node.nodes.entries) { + if (key case YamlScalar(value: String name)) { + final configuration = BuildConfiguration.values + .firstWhereOrNull((element) => element.name == name); + if (configuration != null) { + options[configuration] = CargoBuildOptions.parse(value); + continue; + } + } + throw SourceSpanException( + 'Unknown build configuration. Must be one of ${BuildConfiguration.values.map((e) => e.name)}.', + key.span); + } + } else if (entry.key case YamlScalar(value: 'precompiled_binaries')) { + precompiledBinaries = PrecompiledBinaries.parse(entry.value); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "cargo" or "precompiled_binaries".', + entry.key.span); + } + } + return CargokitCrateOptions( + cargo: options, + precompiledBinaries: precompiledBinaries, + ); + } + + static CargokitCrateOptions load({ + required String manifestDir, + }) { + final uri = Uri.file(path.join(manifestDir, "cargokit.yaml")); + final file = File.fromUri(uri); + if (file.existsSync()) { + final contents = loadYamlNode(file.readAsStringSync(), sourceUrl: uri); + return parse(contents); + } else { + return CargokitCrateOptions(); + } + } +} + +class CargokitUserOptions { + // When Rustup is installed always build locally unless user opts into + // using precompiled binaries. + static bool defaultUsePrecompiledBinaries() { + return Rustup.executablePath() == null; + } + + CargokitUserOptions({ + required this.usePrecompiledBinaries, + required this.verboseLogging, + }); + + CargokitUserOptions._() + : usePrecompiledBinaries = defaultUsePrecompiledBinaries(), + verboseLogging = false; + + static CargokitUserOptions parse(YamlNode node) { + if (node is! YamlMap) { + throw SourceSpanException('Cargokit options must be a map', node.span); + } + bool usePrecompiledBinaries = defaultUsePrecompiledBinaries(); + bool verboseLogging = false; + + for (final entry in node.nodes.entries) { + if (entry.key case YamlScalar(value: 'use_precompiled_binaries')) { + if (entry.value case YamlScalar(value: bool value)) { + usePrecompiledBinaries = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "use_precompiled_binaries". Must be a boolean.', + entry.value.span); + } else if (entry.key case YamlScalar(value: 'verbose_logging')) { + if (entry.value case YamlScalar(value: bool value)) { + verboseLogging = value; + continue; + } + throw SourceSpanException( + 'Invalid value for "verbose_logging". Must be a boolean.', + entry.value.span); + } else { + throw SourceSpanException( + 'Unknown cargokit option type. Must be "use_precompiled_binaries" or "verbose_logging".', + entry.key.span); + } + } + return CargokitUserOptions( + usePrecompiledBinaries: usePrecompiledBinaries, + verboseLogging: verboseLogging, + ); + } + + static CargokitUserOptions load() { + String fileName = "cargokit_options.yaml"; + var userProjectDir = Directory(Environment.rootProjectDir); + + while (userProjectDir.parent.path != userProjectDir.path) { + final configFile = File(path.join(userProjectDir.path, fileName)); + if (configFile.existsSync()) { + final contents = loadYamlNode( + configFile.readAsStringSync(), + sourceUrl: configFile.uri, + ); + final res = parse(contents); + if (res.verboseLogging) { + _log.info('Found user options file at ${configFile.path}'); + } + return res; + } + userProjectDir = userProjectDir.parent; + } + return CargokitUserOptions._(); + } + + final bool usePrecompiledBinaries; + final bool verboseLogging; +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/precompile_binaries.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/precompile_binaries.dart new file mode 100644 index 00000000..c27f4195 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/precompile_binaries.dart @@ -0,0 +1,202 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:github/github.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'artifacts_provider.dart'; +import 'builder.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'rustup.dart'; +import 'target.dart'; + +final _log = Logger('precompile_binaries'); + +class PrecompileBinaries { + PrecompileBinaries({ + required this.privateKey, + required this.githubToken, + required this.repositorySlug, + required this.manifestDir, + required this.targets, + this.androidSdkLocation, + this.androidNdkVersion, + this.androidMinSdkVersion, + this.tempDir, + }); + + final PrivateKey privateKey; + final String githubToken; + final RepositorySlug repositorySlug; + final String manifestDir; + final List targets; + final String? androidSdkLocation; + final String? androidNdkVersion; + final int? androidMinSdkVersion; + final String? tempDir; + + static String fileName(Target target, String name) { + return '${target.rust}_$name'; + } + + static String signatureFileName(Target target, String name) { + return '${target.rust}_$name.sig'; + } + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final targets = List.of(this.targets); + if (targets.isEmpty) { + targets.addAll([ + ...Target.buildableTargets(), + if (androidSdkLocation != null) ...Target.androidTargets(), + ]); + } + + _log.info('Precompiling binaries for $targets'); + + final hash = CrateHash.compute(manifestDir); + _log.info('Computed crate hash: $hash'); + + final String tagName = 'precompiled_$hash'; + + final github = GitHub(auth: Authentication.withToken(githubToken)); + final repo = github.repositories; + final release = await _getOrCreateRelease( + repo: repo, + tagName: tagName, + packageName: crateInfo.packageName, + hash: hash, + ); + + final tempDir = this.tempDir != null + ? Directory(this.tempDir!) + : Directory.systemTemp.createTempSync('precompiled_'); + + tempDir.createSync(recursive: true); + + final crateOptions = CargokitCrateOptions.load( + manifestDir: manifestDir, + ); + + final buildEnvironment = BuildEnvironment( + configuration: BuildConfiguration.release, + crateOptions: crateOptions, + targetTempDir: tempDir.path, + manifestDir: manifestDir, + crateInfo: crateInfo, + isAndroid: androidSdkLocation != null, + androidSdkPath: androidSdkLocation, + androidNdkVersion: androidNdkVersion, + androidMinSdkVersion: androidMinSdkVersion, + ); + + final rustup = Rustup(); + + for (final target in targets) { + final artifactNames = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + if (artifactNames.every((name) { + final fileName = PrecompileBinaries.fileName(target, name); + return (release.assets ?? []).any((e) => e.name == fileName); + })) { + _log.info("All artifacts for $target already exist - skipping"); + continue; + } + + _log.info('Building for $target'); + + final builder = + RustBuilder(target: target, environment: buildEnvironment); + builder.prepare(rustup); + final res = await builder.build(); + + final assets = []; + for (final name in artifactNames) { + final file = File(path.join(res, name)); + if (!file.existsSync()) { + throw Exception('Missing artifact: ${file.path}'); + } + + final data = file.readAsBytesSync(); + final create = CreateReleaseAsset( + name: PrecompileBinaries.fileName(target, name), + contentType: "application/octet-stream", + assetData: data, + ); + final signature = sign(privateKey, data); + final signatureCreate = CreateReleaseAsset( + name: signatureFileName(target, name), + contentType: "application/octet-stream", + assetData: signature, + ); + bool verified = verify(public(privateKey), data, signature); + if (!verified) { + throw Exception('Signature verification failed'); + } + assets.add(create); + assets.add(signatureCreate); + } + _log.info('Uploading assets: ${assets.map((e) => e.name)}'); + for (final asset in assets) { + // This seems to be failing on CI so do it one by one + int retryCount = 0; + while (true) { + try { + await repo.uploadReleaseAssets(release, [asset]); + break; + } on Exception catch (e) { + if (retryCount == 10) { + rethrow; + } + ++retryCount; + _log.shout( + 'Upload failed (attempt $retryCount, will retry): ${e.toString()}'); + await Future.delayed(Duration(seconds: 2)); + } + } + } + } + + _log.info('Cleaning up'); + tempDir.deleteSync(recursive: true); + } + + Future _getOrCreateRelease({ + required RepositoriesService repo, + required String tagName, + required String packageName, + required String hash, + }) async { + Release release; + try { + _log.info('Fetching release $tagName'); + release = await repo.getReleaseByTagName(repositorySlug, tagName); + } on ReleaseNotFound { + _log.info('Release not found - creating release $tagName'); + release = await repo.createRelease( + repositorySlug, + CreateRelease.from( + tagName: tagName, + name: 'Precompiled binaries ${hash.substring(0, 8)}', + targetCommitish: null, + isDraft: false, + isPrerelease: false, + body: 'Precompiled binaries for crate $packageName, ' + 'crate hash $hash.', + )); + } + return release; + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/rustup.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/rustup.dart new file mode 100644 index 00000000..0ac8d086 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/rustup.dart @@ -0,0 +1,136 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:path/path.dart' as path; + +import 'util.dart'; + +class _Toolchain { + _Toolchain( + this.name, + this.targets, + ); + + final String name; + final List targets; +} + +class Rustup { + List? installedTargets(String toolchain) { + final targets = _installedTargets(toolchain); + return targets != null ? List.unmodifiable(targets) : null; + } + + void installToolchain(String toolchain) { + log.info("Installing Rust toolchain: $toolchain"); + runCommand("rustup", ['toolchain', 'install', toolchain]); + _installedToolchains + .add(_Toolchain(toolchain, _getInstalledTargets(toolchain))); + } + + void installTarget( + String target, { + required String toolchain, + }) { + log.info("Installing Rust target: $target"); + runCommand("rustup", [ + 'target', + 'add', + '--toolchain', + toolchain, + target, + ]); + _installedTargets(toolchain)?.add(target); + } + + final List<_Toolchain> _installedToolchains; + + Rustup() : _installedToolchains = _getInstalledToolchains(); + + List? _installedTargets(String toolchain) => _installedToolchains + .firstWhereOrNull( + (e) => e.name == toolchain || e.name.startsWith('$toolchain-')) + ?.targets; + + static List<_Toolchain> _getInstalledToolchains() { + String extractToolchainName(String line) { + // ignore (default) after toolchain name + final parts = line.split(' '); + return parts[0]; + } + + final res = runCommand("rustup", ['toolchain', 'list']); + + // To list all non-custom toolchains, we need to filter out lines that + // don't start with "stable", "beta", or "nightly". + Pattern nonCustom = RegExp(r"^(stable|beta|nightly)"); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty && e.startsWith(nonCustom)) + .map(extractToolchainName) + .toList(growable: true); + + return lines + .map( + (name) => _Toolchain( + name, + _getInstalledTargets(name), + ), + ) + .toList(growable: true); + } + + static List _getInstalledTargets(String toolchain) { + final res = runCommand("rustup", [ + 'target', + 'list', + '--toolchain', + toolchain, + '--installed', + ]); + final lines = res.stdout + .toString() + .split('\n') + .where((e) => e.isNotEmpty) + .toList(growable: true); + return lines; + } + + bool _didInstallRustSrcForNightly = false; + + void installRustSrcForNightly() { + if (_didInstallRustSrcForNightly) { + return; + } + // Useful for -Z build-std + runCommand( + "rustup", + ['component', 'add', 'rust-src', '--toolchain', 'nightly'], + ); + _didInstallRustSrcForNightly = true; + } + + static String? executablePath() { + final envPath = Platform.environment['PATH']; + final envPathSeparator = Platform.isWindows ? ';' : ':'; + final home = Platform.isWindows + ? Platform.environment['USERPROFILE'] + : Platform.environment['HOME']; + final paths = [ + if (home != null) path.join(home, '.cargo', 'bin'), + if (envPath != null) ...envPath.split(envPathSeparator), + ]; + for (final p in paths) { + final rustup = Platform.isWindows ? 'rustup.exe' : 'rustup'; + final rustupPath = path.join(p, rustup); + if (File(rustupPath).existsSync()) { + return rustupPath; + } + } + return null; + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/target.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/target.dart new file mode 100644 index 00000000..6fbc58b6 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/target.dart @@ -0,0 +1,140 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:collection/collection.dart'; + +import 'util.dart'; + +class Target { + Target({ + required this.rust, + this.flutter, + this.android, + this.androidMinSdkVersion, + this.darwinPlatform, + this.darwinArch, + }); + + static final all = [ + Target( + rust: 'armv7-linux-androideabi', + flutter: 'android-arm', + android: 'armeabi-v7a', + androidMinSdkVersion: 16, + ), + Target( + rust: 'aarch64-linux-android', + flutter: 'android-arm64', + android: 'arm64-v8a', + androidMinSdkVersion: 21, + ), + Target( + rust: 'i686-linux-android', + flutter: 'android-x86', + android: 'x86', + androidMinSdkVersion: 16, + ), + Target( + rust: 'x86_64-linux-android', + flutter: 'android-x64', + android: 'x86_64', + androidMinSdkVersion: 21, + ), + Target( + rust: 'x86_64-pc-windows-msvc', + flutter: 'windows-x64', + ), + Target( + rust: 'x86_64-unknown-linux-gnu', + flutter: 'linux-x64', + ), + Target( + rust: 'aarch64-unknown-linux-gnu', + flutter: 'linux-arm64', + ), + Target( + rust: 'x86_64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'x86_64', + ), + Target( + rust: 'aarch64-apple-darwin', + darwinPlatform: 'macosx', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios', + darwinPlatform: 'iphoneos', + darwinArch: 'arm64', + ), + Target( + rust: 'aarch64-apple-ios-sim', + darwinPlatform: 'iphonesimulator', + darwinArch: 'arm64', + ), + Target( + rust: 'x86_64-apple-ios', + darwinPlatform: 'iphonesimulator', + darwinArch: 'x86_64', + ), + ]; + + static Target? forFlutterName(String flutterName) { + return all.firstWhereOrNull((element) => element.flutter == flutterName); + } + + static Target? forDarwin({ + required String platformName, + required String darwinAarch, + }) { + return all.firstWhereOrNull((element) => // + element.darwinPlatform == platformName && + element.darwinArch == darwinAarch); + } + + static Target? forRustTriple(String triple) { + return all.firstWhereOrNull((element) => element.rust == triple); + } + + static List androidTargets() { + return all + .where((element) => element.android != null) + .toList(growable: false); + } + + /// Returns buildable targets on current host platform ignoring Android targets. + static List buildableTargets() { + if (Platform.isLinux) { + // Right now we don't support cross-compiling on Linux. So we just return + // the host target. + final arch = runCommand('arch', []).stdout as String; + if (arch.trim() == 'aarch64') { + return [Target.forRustTriple('aarch64-unknown-linux-gnu')!]; + } else { + return [Target.forRustTriple('x86_64-unknown-linux-gnu')!]; + } + } + return all.where((target) { + if (Platform.isWindows) { + return target.rust.contains('-windows-'); + } else if (Platform.isMacOS) { + return target.darwinPlatform != null; + } + return false; + }).toList(growable: false); + } + + @override + String toString() { + return rust; + } + + final String? flutter; + final String rust; + final String? android; + final int? androidMinSdkVersion; + final String? darwinPlatform; + final String? darwinArch; +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/util.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/util.dart new file mode 100644 index 00000000..8bb6a872 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/util.dart @@ -0,0 +1,172 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:convert'; +import 'dart:io'; + +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as path; + +import 'logging.dart'; +import 'rustup.dart'; + +final log = Logger("process"); + +class CommandFailedException implements Exception { + final String executable; + final List arguments; + final ProcessResult result; + + CommandFailedException({ + required this.executable, + required this.arguments, + required this.result, + }); + + @override + String toString() { + final stdout = result.stdout.toString().trim(); + final stderr = result.stderr.toString().trim(); + return [ + "External Command: $executable ${arguments.map((e) => '"$e"').join(' ')}", + "Returned Exit Code: ${result.exitCode}", + kSeparator, + "STDOUT:", + if (stdout.isNotEmpty) stdout, + kSeparator, + "STDERR:", + if (stderr.isNotEmpty) stderr, + ].join('\n'); + } +} + +class TestRunCommandArgs { + final String executable; + final List arguments; + final String? workingDirectory; + final Map? environment; + final bool includeParentEnvironment; + final bool runInShell; + final Encoding? stdoutEncoding; + final Encoding? stderrEncoding; + + TestRunCommandArgs({ + required this.executable, + required this.arguments, + this.workingDirectory, + this.environment, + this.includeParentEnvironment = true, + this.runInShell = false, + this.stdoutEncoding, + this.stderrEncoding, + }); +} + +class TestRunCommandResult { + TestRunCommandResult({ + this.pid = 1, + this.exitCode = 0, + this.stdout = '', + this.stderr = '', + }); + + final int pid; + final int exitCode; + final String stdout; + final String stderr; +} + +TestRunCommandResult Function(TestRunCommandArgs args)? testRunCommandOverride; + +ProcessResult runCommand( + String executable, + List arguments, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + Encoding? stdoutEncoding = systemEncoding, + Encoding? stderrEncoding = systemEncoding, +}) { + if (testRunCommandOverride != null) { + final result = testRunCommandOverride!(TestRunCommandArgs( + executable: executable, + arguments: arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stdoutEncoding: stdoutEncoding, + stderrEncoding: stderrEncoding, + )); + return ProcessResult( + result.pid, + result.exitCode, + result.stdout, + result.stderr, + ); + } + log.finer('Running command $executable ${arguments.join(' ')}'); + final res = Process.runSync( + _resolveExecutable(executable), + arguments, + workingDirectory: workingDirectory, + environment: environment, + includeParentEnvironment: includeParentEnvironment, + runInShell: runInShell, + stderrEncoding: stderrEncoding, + stdoutEncoding: stdoutEncoding, + ); + if (res.exitCode != 0) { + throw CommandFailedException( + executable: executable, + arguments: arguments, + result: res, + ); + } else { + return res; + } +} + +class RustupNotFoundException implements Exception { + @override + String toString() { + return [ + ' ', + 'rustup not found in PATH.', + ' ', + 'Maybe you need to install Rust? It only takes a minute:', + ' ', + if (Platform.isWindows) 'https://www.rust-lang.org/tools/install', + if (hasHomebrewRustInPath()) ...[ + '\$ brew unlink rust # Unlink homebrew Rust from PATH', + ], + if (!Platform.isWindows) + "\$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh", + ' ', + ].join('\n'); + } + + static bool hasHomebrewRustInPath() { + if (!Platform.isMacOS) { + return false; + } + final envPath = Platform.environment['PATH'] ?? ''; + final paths = envPath.split(':'); + return paths.any((p) { + return p.contains('homebrew') && File(path.join(p, 'rustc')).existsSync(); + }); + } +} + +String _resolveExecutable(String executable) { + if (executable == 'rustup') { + final resolved = Rustup.executablePath(); + if (resolved != null) { + return resolved; + } + throw RustupNotFoundException(); + } else { + return executable; + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/lib/src/verify_binaries.dart b/third_party/convex_flutter/cargokit/build_tool/lib/src/verify_binaries.dart new file mode 100644 index 00000000..2366b57b --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/lib/src/verify_binaries.dart @@ -0,0 +1,84 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import 'dart:io'; + +import 'package:ed25519_edwards/ed25519_edwards.dart'; +import 'package:http/http.dart'; + +import 'artifacts_provider.dart'; +import 'cargo.dart'; +import 'crate_hash.dart'; +import 'options.dart'; +import 'precompile_binaries.dart'; +import 'target.dart'; + +class VerifyBinaries { + VerifyBinaries({ + required this.manifestDir, + }); + + final String manifestDir; + + Future run() async { + final crateInfo = CrateInfo.load(manifestDir); + + final config = CargokitCrateOptions.load(manifestDir: manifestDir); + final precompiledBinaries = config.precompiledBinaries; + if (precompiledBinaries == null) { + stdout.writeln('Crate does not support precompiled binaries.'); + } else { + final crateHash = CrateHash.compute(manifestDir); + stdout.writeln('Crate hash: $crateHash'); + + for (final target in Target.all) { + final message = 'Checking ${target.rust}...'; + stdout.write(message.padRight(40)); + stdout.flush(); + + final artifacts = getArtifactNames( + target: target, + libraryName: crateInfo.packageName, + remote: true, + ); + + final prefix = precompiledBinaries.uriPrefix; + + bool ok = true; + + for (final artifact in artifacts) { + final fileName = PrecompileBinaries.fileName(target, artifact); + final signatureFileName = + PrecompileBinaries.signatureFileName(target, artifact); + + final url = Uri.parse('$prefix$crateHash/$fileName'); + final signatureUrl = + Uri.parse('$prefix$crateHash/$signatureFileName'); + + final signature = await get(signatureUrl); + if (signature.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + final asset = await get(url); + if (asset.statusCode != 200) { + stdout.writeln('MISSING'); + ok = false; + break; + } + + if (!verify(precompiledBinaries.publicKey, asset.bodyBytes, + signature.bodyBytes)) { + stdout.writeln('INVALID SIGNATURE'); + ok = false; + } + } + + if (ok) { + stdout.writeln('OK'); + } + } + } + } +} diff --git a/third_party/convex_flutter/cargokit/build_tool/pubspec.lock b/third_party/convex_flutter/cargokit/build_tool/pubspec.lock new file mode 100644 index 00000000..343bdd36 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/pubspec.lock @@ -0,0 +1,453 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + url: "https://pub.dev" + source: hosted + version: "64.0.0" + adaptive_number: + dependency: transitive + description: + name: adaptive_number + sha256: "3a567544e9b5c9c803006f51140ad544aedc79604fd4f3f2c1380003f97c1d77" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + url: "https://pub.dev" + source: hosted + version: "6.2.0" + args: + dependency: "direct main" + description: + name: args + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + async: + dependency: transitive + description: + name: async + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + url: "https://pub.dev" + source: hosted + version: "2.11.0" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + collection: + dependency: "direct main" + description: + name: collection + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + url: "https://pub.dev" + source: hosted + version: "1.18.0" + convert: + dependency: "direct main" + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + coverage: + dependency: transitive + description: + name: coverage + sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097" + url: "https://pub.dev" + source: hosted + version: "1.6.3" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" + ed25519_edwards: + dependency: "direct main" + description: + name: ed25519_edwards + sha256: "6ce0112d131327ec6d42beede1e5dfd526069b18ad45dcf654f15074ad9276cd" + url: "https://pub.dev" + source: hosted + version: "0.3.1" + file: + dependency: transitive + description: + name: file + sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + github: + dependency: "direct main" + description: + name: github + sha256: "9966bc13bf612342e916b0a343e95e5f046c88f602a14476440e9b75d2295411" + url: "https://pub.dev" + source: hosted + version: "9.17.0" + glob: + dependency: transitive + description: + name: glob + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + hex: + dependency: "direct main" + description: + name: hex + sha256: "4e7cd54e4b59ba026432a6be2dd9d96e4c5205725194997193bf871703b82c4a" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + http: + dependency: "direct main" + description: + name: http + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + url: "https://pub.dev" + source: hosted + version: "4.0.2" + io: + dependency: transitive + description: + name: io + sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + js: + dependency: transitive + description: + name: js + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 + url: "https://pub.dev" + source: hosted + version: "0.6.7" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 + url: "https://pub.dev" + source: hosted + version: "4.8.1" + lints: + dependency: "direct dev" + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + logging: + dependency: "direct main" + description: + name: logging + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + url: "https://pub.dev" + source: hosted + version: "0.12.16" + meta: + dependency: transitive + description: + name: meta + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + mime: + dependency: transitive + description: + name: mime + sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + url: "https://pub.dev" + source: hosted + version: "1.0.4" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" + path: + dependency: "direct main" + description: + name: path + sha256: "2ad4cddff7f5cc0e2d13069f2a3f7a73ca18f66abd6f5ecf215219cdb3638edb" + url: "https://pub.dev" + source: hosted + version: "1.8.0" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 + url: "https://pub.dev" + source: hosted + version: "5.4.0" + pool: + dependency: transitive + description: + name: pool + sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + shelf: + dependency: transitive + description: + name: shelf + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 + url: "https://pub.dev" + source: hosted + version: "1.4.1" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e + url: "https://pub.dev" + source: hosted + version: "1.1.2" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + url: "https://pub.dev" + source: hosted + version: "1.0.4" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae" + url: "https://pub.dev" + source: hosted + version: "2.1.1" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703" + url: "https://pub.dev" + source: hosted + version: "0.10.12" + source_span: + dependency: "direct main" + description: + name: source_span + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + url: "https://pub.dev" + source: hosted + version: "1.10.0" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + url: "https://pub.dev" + source: hosted + version: "1.11.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + url: "https://pub.dev" + source: hosted + version: "2.1.2" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + url: "https://pub.dev" + source: hosted + version: "1.2.1" + test: + dependency: "direct dev" + description: + name: test + sha256: "9b0dd8e36af4a5b1569029949d50a52cb2a2a2fdaa20cebb96e6603b9ae241f9" + url: "https://pub.dev" + source: hosted + version: "1.24.6" + test_api: + dependency: transitive + description: + name: test_api + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + url: "https://pub.dev" + source: hosted + version: "0.6.1" + test_core: + dependency: transitive + description: + name: test_core + sha256: "4bef837e56375537055fdbbbf6dd458b1859881f4c7e6da936158f77d61ab265" + url: "https://pub.dev" + source: hosted + version: "0.5.6" + toml: + dependency: "direct main" + description: + name: toml + sha256: "157c5dca5160fced243f3ce984117f729c788bb5e475504f3dbcda881accee44" + url: "https://pub.dev" + source: hosted + version: "0.14.0" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + version: + dependency: "direct main" + description: + name: version + sha256: "2307e23a45b43f96469eeab946208ed63293e8afca9c28cd8b5241ff31c55f55" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "0fae432c85c4ea880b33b497d32824b97795b04cdaa74d270219572a1f50268d" + url: "https://pub.dev" + source: hosted + version: "11.9.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "67d3a8b6c79e1987d19d848b0892e582dbb0c66c57cc1fef58a177dd2aa2823d" + url: "https://pub.dev" + source: hosted + version: "1.2.0" + yaml: + dependency: "direct main" + description: + name: yaml + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" + url: "https://pub.dev" + source: hosted + version: "3.1.2" +sdks: + dart: ">=3.0.0 <4.0.0" diff --git a/third_party/convex_flutter/cargokit/build_tool/pubspec.yaml b/third_party/convex_flutter/cargokit/build_tool/pubspec.yaml new file mode 100644 index 00000000..18c61e33 --- /dev/null +++ b/third_party/convex_flutter/cargokit/build_tool/pubspec.yaml @@ -0,0 +1,33 @@ +# This is copied from Cargokit (which is the official way to use it currently) +# Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +name: build_tool +description: Cargokit build_tool. Facilitates the build of Rust crate during Flutter application build. +publish_to: none +version: 1.0.0 + +environment: + sdk: ">=3.0.0 <4.0.0" + +# Add regular dependencies here. +dependencies: + # these are pinned on purpose because the bundle_tool_runner doesn't have + # pubspec.lock. See run_build_tool.sh + logging: 1.2.0 + path: 1.8.0 + version: 3.0.0 + collection: 1.18.0 + ed25519_edwards: 0.3.1 + hex: 0.2.0 + yaml: 3.1.2 + source_span: 1.10.0 + github: 9.17.0 + args: 2.4.2 + crypto: 3.0.3 + convert: 3.1.1 + http: 1.1.0 + toml: 0.14.0 + +dev_dependencies: + lints: ^2.1.0 + test: ^1.24.0 diff --git a/third_party/convex_flutter/cargokit/cmake/cargokit.cmake b/third_party/convex_flutter/cargokit/cmake/cargokit.cmake new file mode 100644 index 00000000..ddd05df9 --- /dev/null +++ b/third_party/convex_flutter/cargokit/cmake/cargokit.cmake @@ -0,0 +1,99 @@ +SET(cargokit_cmake_root "${CMAKE_CURRENT_LIST_DIR}/..") + +# Workaround for https://github.com/dart-lang/pub/issues/4010 +get_filename_component(cargokit_cmake_root "${cargokit_cmake_root}" REALPATH) + +if(WIN32) + # REALPATH does not properly resolve symlinks on windows :-/ + execute_process(COMMAND powershell -ExecutionPolicy Bypass -File "${CMAKE_CURRENT_LIST_DIR}/resolve_symlinks.ps1" "${cargokit_cmake_root}" OUTPUT_VARIABLE cargokit_cmake_root OUTPUT_STRIP_TRAILING_WHITESPACE) +endif() + +# Arguments +# - target: CMAKE target to which rust library is linked +# - manifest_dir: relative path from current folder to directory containing cargo manifest +# - lib_name: cargo package name +# - any_symbol_name: name of any exported symbol from the library. +# used on windows to force linking with library. +function(apply_cargokit target manifest_dir lib_name any_symbol_name) + + set(CARGOKIT_LIB_NAME "${lib_name}") + set(CARGOKIT_LIB_FULL_NAME "${CMAKE_SHARED_MODULE_PREFIX}${CARGOKIT_LIB_NAME}${CMAKE_SHARED_MODULE_SUFFIX}") + if (CMAKE_CONFIGURATION_TYPES) + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/$") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/$/${CARGOKIT_LIB_FULL_NAME}") + else() + set(CARGOKIT_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}") + set(OUTPUT_LIB "${CMAKE_CURRENT_BINARY_DIR}/${CARGOKIT_LIB_FULL_NAME}") + endif() + set(CARGOKIT_TEMP_DIR "${CMAKE_CURRENT_BINARY_DIR}/cargokit_build") + + if (FLUTTER_TARGET_PLATFORM) + set(CARGOKIT_TARGET_PLATFORM "${FLUTTER_TARGET_PLATFORM}") + else() + set(CARGOKIT_TARGET_PLATFORM "windows-x64") + endif() + + set(CARGOKIT_ENV + "CARGOKIT_CMAKE=${CMAKE_COMMAND}" + "CARGOKIT_CONFIGURATION=$" + "CARGOKIT_MANIFEST_DIR=${CMAKE_CURRENT_SOURCE_DIR}/${manifest_dir}" + "CARGOKIT_TARGET_TEMP_DIR=${CARGOKIT_TEMP_DIR}" + "CARGOKIT_OUTPUT_DIR=${CARGOKIT_OUTPUT_DIR}" + "CARGOKIT_TARGET_PLATFORM=${CARGOKIT_TARGET_PLATFORM}" + "CARGOKIT_TOOL_TEMP_DIR=${CARGOKIT_TEMP_DIR}/tool" + "CARGOKIT_ROOT_PROJECT_DIR=${CMAKE_SOURCE_DIR}" + ) + + if (WIN32) + set(SCRIPT_EXTENSION ".cmd") + set(IMPORT_LIB_EXTENSION ".lib") + else() + set(SCRIPT_EXTENSION ".sh") + set(IMPORT_LIB_EXTENSION "") + execute_process(COMMAND chmod +x "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}") + endif() + + # Using generators in custom command is only supported in CMake 3.20+ + if (CMAKE_CONFIGURATION_TYPES AND ${CMAKE_VERSION} VERSION_LESS "3.20.0") + foreach(CONFIG IN LISTS CMAKE_CONFIGURATION_TYPES) + add_custom_command( + OUTPUT + "${CMAKE_CURRENT_BINARY_DIR}/${CONFIG}/${CARGOKIT_LIB_FULL_NAME}" + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endforeach() + else() + add_custom_command( + OUTPUT + ${OUTPUT_LIB} + "${CMAKE_CURRENT_BINARY_DIR}/_phony_" + COMMAND ${CMAKE_COMMAND} -E env ${CARGOKIT_ENV} + "${cargokit_cmake_root}/run_build_tool${SCRIPT_EXTENSION}" build-cmake + VERBATIM + ) + endif() + + + set_source_files_properties("${CMAKE_CURRENT_BINARY_DIR}/_phony_" PROPERTIES SYMBOLIC TRUE) + + if (TARGET ${target}) + # If we have actual cmake target provided create target and make existing + # target depend on it + add_custom_target("${target}_cargokit" DEPENDS ${OUTPUT_LIB}) + add_dependencies("${target}" "${target}_cargokit") + target_link_libraries("${target}" PRIVATE "${OUTPUT_LIB}${IMPORT_LIB_EXTENSION}") + if(WIN32) + target_link_options(${target} PRIVATE "/INCLUDE:${any_symbol_name}") + endif() + else() + # Otherwise (FFI) just use ALL to force building always + add_custom_target("${target}_cargokit" ALL DEPENDS ${OUTPUT_LIB}) + endif() + + # Allow adding the output library to plugin bundled libraries + set("${target}_cargokit_lib" ${OUTPUT_LIB} PARENT_SCOPE) + +endfunction() diff --git a/third_party/convex_flutter/cargokit/cmake/resolve_symlinks.ps1 b/third_party/convex_flutter/cargokit/cmake/resolve_symlinks.ps1 new file mode 100644 index 00000000..2ac593a1 --- /dev/null +++ b/third_party/convex_flutter/cargokit/cmake/resolve_symlinks.ps1 @@ -0,0 +1,34 @@ +function Resolve-Symlinks { + [CmdletBinding()] + [OutputType([string])] + param( + [Parameter(Position = 0, Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] + [string] $Path + ) + + [string] $separator = '/' + [string[]] $parts = $Path.Split($separator) + + [string] $realPath = '' + foreach ($part in $parts) { + if ($realPath -and !$realPath.EndsWith($separator)) { + $realPath += $separator + } + + $realPath += $part.Replace('\', '/') + + # The slash is important when using Get-Item on Drive letters in pwsh. + if (-not($realPath.Contains($separator)) -and $realPath.EndsWith(':')) { + $realPath += '/' + } + + $item = Get-Item $realPath + if ($item.LinkTarget) { + $realPath = $item.LinkTarget.Replace('\', '/') + } + } + $realPath +} + +$path = Resolve-Symlinks -Path $args[0] +Write-Host $path diff --git a/third_party/convex_flutter/cargokit/gradle/plugin.gradle b/third_party/convex_flutter/cargokit/gradle/plugin.gradle new file mode 100644 index 00000000..4af35ee0 --- /dev/null +++ b/third_party/convex_flutter/cargokit/gradle/plugin.gradle @@ -0,0 +1,179 @@ +/// This is copied from Cargokit (which is the official way to use it currently) +/// Details: https://fzyzcjy.github.io/flutter_rust_bridge/manual/integrate/builtin + +import java.nio.file.Paths +import org.apache.tools.ant.taskdefs.condition.Os + +CargoKitPlugin.file = buildscript.sourceFile + +apply plugin: CargoKitPlugin + +class CargoKitExtension { + String manifestDir; // Relative path to folder containing Cargo.toml + String libname; // Library name within Cargo.toml. Must be a cdylib +} + +abstract class CargoKitBuildTask extends DefaultTask { + + @Input + String buildMode + + @Input + String buildDir + + @Input + String outputDir + + @Input + String ndkVersion + + @Input + String sdkDirectory + + @Input + int compileSdkVersion; + + @Input + int minSdkVersion; + + @Input + String pluginFile + + @Input + List targetPlatforms + + @TaskAction + def build() { + if (project.cargokit.manifestDir == null) { + throw new GradleException("Property 'manifestDir' must be set on cargokit extension"); + } + + if (project.cargokit.libname == null) { + throw new GradleException("Property 'libname' must be set on cargokit extension"); + } + + def executableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "run_build_tool.cmd" : "run_build_tool.sh" + def path = Paths.get(new File(pluginFile).parent, "..", executableName); + + def manifestDir = Paths.get(project.buildscript.sourceFile.parent, project.cargokit.manifestDir) + + def rootProjectDir = project.rootProject.projectDir + + if (!Os.isFamily(Os.FAMILY_WINDOWS)) { + project.exec { + commandLine 'chmod', '+x', path + } + } + + project.exec { + executable path + args "build-gradle" + environment "CARGOKIT_ROOT_PROJECT_DIR", rootProjectDir + environment "CARGOKIT_TOOL_TEMP_DIR", "${buildDir}/build_tool" + environment "CARGOKIT_MANIFEST_DIR", manifestDir + environment "CARGOKIT_CONFIGURATION", buildMode + environment "CARGOKIT_TARGET_TEMP_DIR", buildDir + environment "CARGOKIT_OUTPUT_DIR", outputDir + environment "CARGOKIT_NDK_VERSION", ndkVersion + environment "CARGOKIT_SDK_DIR", sdkDirectory + environment "CARGOKIT_COMPILE_SDK_VERSION", compileSdkVersion + environment "CARGOKIT_MIN_SDK_VERSION", minSdkVersion + environment "CARGOKIT_TARGET_PLATFORMS", targetPlatforms.join(",") + environment "CARGOKIT_JAVA_HOME", System.properties['java.home'] + } + } +} + +class CargoKitPlugin implements Plugin { + + static String file; + + private Plugin findFlutterPlugin(Project rootProject) { + _findFlutterPlugin(rootProject.childProjects) + } + + private Plugin _findFlutterPlugin(Map projects) { + for (project in projects) { + for (plugin in project.value.getPlugins()) { + if (plugin.class.name == "com.flutter.gradle.FlutterPlugin") { + return plugin; + } + } + def plugin = _findFlutterPlugin(project.value.childProjects); + if (plugin != null) { + return plugin; + } + } + return null; + } + + @Override + void apply(Project project) { + def plugin = findFlutterPlugin(project.rootProject); + + project.extensions.create("cargokit", CargoKitExtension) + + if (plugin == null) { + print("Flutter plugin not found, CargoKit plugin will not be applied.") + return; + } + + def cargoBuildDir = "${project.buildDir}/build" + + // Determine if the project is an application or library + def isApplication = plugin.project.plugins.hasPlugin('com.android.application') + def variants = isApplication ? plugin.project.android.applicationVariants : plugin.project.android.libraryVariants + + variants.all { variant -> + + final buildType = variant.buildType.name + + def cargoOutputDir = "${project.buildDir}/jniLibs/${buildType}"; + def jniLibs = project.android.sourceSets.maybeCreate(buildType).jniLibs; + jniLibs.srcDir(new File(cargoOutputDir)) + + def platforms = com.flutter.gradle.FlutterPluginUtils.getTargetPlatforms(project).collect() + + // Same thing addFlutterDependencies does in flutter.gradle + if (buildType == "debug") { + platforms.add("android-x86") + platforms.add("android-x64") + } + + // The task name depends on plugin properties, which are not available + // at this point + project.getGradle().afterProject { + def taskName = "cargokitCargoBuild${project.cargokit.libname.capitalize()}${buildType.capitalize()}"; + + if (project.tasks.findByName(taskName)) { + return + } + + if (plugin.project.android.ndkVersion == null) { + throw new GradleException("Please set 'android.ndkVersion' in 'app/build.gradle'.") + } + + def task = project.tasks.create(taskName, CargoKitBuildTask.class) { + buildMode = variant.buildType.name + buildDir = cargoBuildDir + outputDir = cargoOutputDir + ndkVersion = plugin.project.android.ndkVersion + sdkDirectory = plugin.project.android.sdkDirectory + minSdkVersion = plugin.project.android.defaultConfig.minSdkVersion.apiLevel as int + compileSdkVersion = plugin.project.android.compileSdkVersion.substring(8) as int + targetPlatforms = platforms + pluginFile = CargoKitPlugin.file + } + def onTask = { newTask -> + if (newTask.name == "merge${buildType.capitalize()}NativeLibs") { + newTask.dependsOn task + // Fix gradle 7.4.2 not picking up JNI library changes + newTask.outputs.upToDateWhen { false } + } + } + project.tasks.each onTask + project.tasks.whenTaskAdded onTask + } + } + } +} diff --git a/third_party/convex_flutter/cargokit/run_build_tool.cmd b/third_party/convex_flutter/cargokit/run_build_tool.cmd new file mode 100755 index 00000000..c45d0aa8 --- /dev/null +++ b/third_party/convex_flutter/cargokit/run_build_tool.cmd @@ -0,0 +1,91 @@ +@echo off +setlocal + +setlocal ENABLEDELAYEDEXPANSION + +SET BASEDIR=%~dp0 + +if not exist "%CARGOKIT_TOOL_TEMP_DIR%" ( + mkdir "%CARGOKIT_TOOL_TEMP_DIR%" +) +cd /D "%CARGOKIT_TOOL_TEMP_DIR%" + +SET BUILD_TOOL_PKG_DIR=%BASEDIR%build_tool +SET DART=%FLUTTER_ROOT%\bin\cache\dart-sdk\bin\dart + +set BUILD_TOOL_PKG_DIR_POSIX=%BUILD_TOOL_PKG_DIR:\=/% + +( + echo name: build_tool_runner + echo version: 1.0.0 + echo publish_to: none + echo. + echo environment: + echo sdk: '^>=3.0.0 ^<4.0.0' + echo. + echo dependencies: + echo build_tool: + echo path: %BUILD_TOOL_PKG_DIR_POSIX% +) >pubspec.yaml + +if not exist bin ( + mkdir bin +) + +( + echo import 'package:build_tool/build_tool.dart' as build_tool; + echo void main^(List^ args^) ^{ + echo build_tool.runMain^(args^); + echo ^} +) >bin\build_tool_runner.dart + +SET PRECOMPILED=bin\build_tool_runner.dill + +REM To detect changes in package we compare output of DIR /s (recursive) +set PREV_PACKAGE_INFO=.dart_tool\package_info.prev +set CUR_PACKAGE_INFO=.dart_tool\package_info.cur + +DIR "%BUILD_TOOL_PKG_DIR%" /s > "%CUR_PACKAGE_INFO%_orig" + +REM Last line in dir output is free space on harddrive. That is bound to +REM change between invocation so we need to remove it +( + Set "Line=" + For /F "UseBackQ Delims=" %%A In ("%CUR_PACKAGE_INFO%_orig") Do ( + SetLocal EnableDelayedExpansion + If Defined Line Echo !Line! + EndLocal + Set "Line=%%A") +) >"%CUR_PACKAGE_INFO%" +DEL "%CUR_PACKAGE_INFO%_orig" + +REM Compare current directory listing with previous +FC /B "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" > nul 2>&1 + +If %ERRORLEVEL% neq 0 ( + REM Changed - copy current to previous and remove precompiled kernel + if exist "%PREV_PACKAGE_INFO%" ( + DEL "%PREV_PACKAGE_INFO%" + ) + MOVE /Y "%CUR_PACKAGE_INFO%" "%PREV_PACKAGE_INFO%" + if exist "%PRECOMPILED%" ( + DEL "%PRECOMPILED%" + ) +) + +REM There is no CUR_PACKAGE_INFO it was renamed in previous step to %PREV_PACKAGE_INFO% +REM which means we need to do pub get and precompile +if not exist "%PRECOMPILED%" ( + echo Running pub get in "%cd%" + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart +) + +"%DART%" "%PRECOMPILED%" %* + +REM 253 means invalid snapshot version. +If %ERRORLEVEL% equ 253 ( + "%DART%" pub get --no-precompile + "%DART%" compile kernel bin/build_tool_runner.dart + "%DART%" "%PRECOMPILED%" %* +) diff --git a/third_party/convex_flutter/cargokit/run_build_tool.sh b/third_party/convex_flutter/cargokit/run_build_tool.sh new file mode 100755 index 00000000..24b0ed89 --- /dev/null +++ b/third_party/convex_flutter/cargokit/run_build_tool.sh @@ -0,0 +1,99 @@ +#!/usr/bin/env bash + +set -e + +BASEDIR=$(dirname "$0") + +mkdir -p "$CARGOKIT_TOOL_TEMP_DIR" + +cd "$CARGOKIT_TOOL_TEMP_DIR" + +# Write a very simple bin package in temp folder that depends on build_tool package +# from Cargokit. This is done to ensure that we don't pollute Cargokit folder +# with .dart_tool contents. + +BUILD_TOOL_PKG_DIR="$BASEDIR/build_tool" + +if [[ -z $FLUTTER_ROOT ]]; then # not defined + DART=dart +else + DART="$FLUTTER_ROOT/bin/cache/dart-sdk/bin/dart" +fi + +cat << EOF > "pubspec.yaml" +name: build_tool_runner +version: 1.0.0 +publish_to: none + +environment: + sdk: '>=3.0.0 <4.0.0' + +dependencies: + build_tool: + path: "$BUILD_TOOL_PKG_DIR" +EOF + +mkdir -p "bin" + +cat << EOF > "bin/build_tool_runner.dart" +import 'package:build_tool/build_tool.dart' as build_tool; +void main(List args) { + build_tool.runMain(args); +} +EOF + +# Create alias for `shasum` if it does not exist and `sha1sum` exists +if ! [ -x "$(command -v shasum)" ] && [ -x "$(command -v sha1sum)" ]; then + shopt -s expand_aliases + alias shasum="sha1sum" +fi + +# Dart run will not cache any package that has a path dependency, which +# is the case for our build_tool_runner. So instead we precompile the package +# ourselves. +# To invalidate the cached kernel we use the hash of ls -LR of the build_tool +# package directory. This should be good enough, as the build_tool package +# itself is not meant to have any path dependencies. + +if [[ "$OSTYPE" == "darwin"* ]]; then + PACKAGE_HASH=$(ls -lTR "$BUILD_TOOL_PKG_DIR" | shasum) +else + PACKAGE_HASH=$(ls -lR --full-time "$BUILD_TOOL_PKG_DIR" | shasum) +fi + +PACKAGE_HASH_FILE=".package_hash" + +if [ -f "$PACKAGE_HASH_FILE" ]; then + EXISTING_HASH=$(cat "$PACKAGE_HASH_FILE") + if [ "$PACKAGE_HASH" != "$EXISTING_HASH" ]; then + rm "$PACKAGE_HASH_FILE" + fi +fi + +# Run pub get if needed. +if [ ! -f "$PACKAGE_HASH_FILE" ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + echo "$PACKAGE_HASH" > "$PACKAGE_HASH_FILE" +fi + +# Rebuild the tool if it was deleted by Android Studio +if [ ! -f "bin/build_tool_runner.dill" ]; then + "$DART" compile kernel bin/build_tool_runner.dart +fi + +set +e + +"$DART" bin/build_tool_runner.dill "$@" + +exit_code=$? + +# 253 means invalid snapshot version. +if [ $exit_code == 253 ]; then + "$DART" pub get --no-precompile + "$DART" compile kernel bin/build_tool_runner.dart + "$DART" bin/build_tool_runner.dill "$@" + exit_code=$? +fi + +exit $exit_code diff --git a/third_party/convex_flutter/example/HEALTH_CHECK.md b/third_party/convex_flutter/example/HEALTH_CHECK.md new file mode 100644 index 00000000..47296f78 --- /dev/null +++ b/third_party/convex_flutter/example/HEALTH_CHECK.md @@ -0,0 +1,87 @@ +# Health Check Query Setup (Optional) + +To use the health check functionality in this example app, you can optionally create a simple health check query in your Convex backend. This is **recommended but not required** - you can use any existing query instead. + +## Why Create a Dedicated Health Check? + +A dedicated health check query provides: +- **Lightweight**: No database queries or complex logic +- **Fast**: Minimal processing time +- **Clear Purpose**: Obviously for health checks +- **No Side Effects**: Doesn't modify any data +- **Best Practice**: Follows Convex and REST API conventions + +However, if you prefer, you can use any existing lightweight query (like `users:count` or `messages:list`) instead. + +## Creating the Health Check Query + +Create a file `convex/health.ts` in your Convex backend with the following content: + +```typescript +// convex/health.ts +import { query } from "./_generated/server"; + +export const ping = query({ + args: {}, + handler: async () => { + return "ok"; + }, +}); +``` + +This creates a lightweight query endpoint at `health:ping` that: +- Takes no arguments +- Returns a simple "ok" response +- Can be used for connection health checks +- Has minimal overhead + +## Using the Health Check + +The example app uses this query in two ways: + +### 1. Automatic Connection on Startup + +The HomeScreen triggers the health check automatically when the app starts: + +```dart +await ConvexClient.instance.query('health:ping', {}); +``` + +This establishes the WebSocket connection immediately, allowing the connection state to transition from "connecting" to "connected". + +### 2. Manual Connection Checks (Deprecated) + +The deprecated `checkConnection()` method uses the configured `healthCheckQuery`: + +```dart +await ConvexClient.initialize( + ConvexConfig( + healthCheckQuery: "health:ping", + ), +); + +final status = await ConvexClient.instance.checkConnection(); +``` + +## Why Use a Dedicated Health Check Query? + +1. **Lightweight**: No database queries or complex logic +2. **Fast**: Minimal processing time +3. **Idempotent**: Safe to call repeatedly +4. **No Side Effects**: Doesn't modify any data +5. **Clear Purpose**: Obvious what it's for + +## Alternative + +If you don't want to create a dedicated health check query, you can use any existing lightweight query from your backend: + +```dart +// Use any existing query +await ConvexClient.initialize( + ConvexConfig( + healthCheckQuery: "users:count", // Any lightweight query + ), +); +``` + +However, a dedicated health check endpoint is the recommended best practice. diff --git a/third_party/convex_flutter/example/README.md b/third_party/convex_flutter/example/README.md new file mode 100644 index 00000000..065bb074 --- /dev/null +++ b/third_party/convex_flutter/example/README.md @@ -0,0 +1,20 @@ +# convex_flutter_example + +Demonstrates how to use the convex_flutter plugin. + +## Usage Example + +Here's an example of how to send a message using a Convex mutation: + +```dart +await ConvexClient.instance.mutation( + name: "messages:send", + args: {"body": message, "author": "Singh"}, +); +``` + +Here's an example of how to query the backend: + +```dart +final result = await client.query('your_query'); +``` diff --git a/third_party/convex_flutter/example/analysis_options.yaml b/third_party/convex_flutter/example/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/third_party/convex_flutter/example/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/third_party/convex_flutter/example/android/app/build.gradle.kts b/third_party/convex_flutter/example/android/app/build.gradle.kts new file mode 100644 index 00000000..24f02a12 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/build.gradle.kts @@ -0,0 +1,44 @@ +plugins { + id("com.android.application") + id("kotlin-android") + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. + id("dev.flutter.flutter-gradle-plugin") +} + +android { + namespace = "com.example.convex_flutter_example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } + + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId = "com.example.convex_flutter_example" + // You can update the following values to match your application needs. + // For more information, see: https://flutter.dev/to/review-gradle-config. + minSdk = flutter.minSdkVersion + targetSdk = flutter.targetSdkVersion + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") + } + } +} + +flutter { + source = "../.." +} diff --git a/third_party/convex_flutter/example/android/app/src/debug/AndroidManifest.xml b/third_party/convex_flutter/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/third_party/convex_flutter/example/android/app/src/main/AndroidManifest.xml b/third_party/convex_flutter/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..0be63e96 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/android/app/src/main/kotlin/com/example/convex_flutter_example/MainActivity.kt b/third_party/convex_flutter/example/android/app/src/main/kotlin/com/example/convex_flutter_example/MainActivity.kt new file mode 100644 index 00000000..fe5ed24f --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/kotlin/com/example/convex_flutter_example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.convex_flutter_example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/third_party/convex_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml b/third_party/convex_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 00000000..f74085f3 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/third_party/convex_flutter/example/android/app/src/main/res/drawable/launch_background.xml b/third_party/convex_flutter/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/third_party/convex_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..db77bb4b Binary files /dev/null and b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/third_party/convex_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..17987b79 Binary files /dev/null and b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..09d43914 Binary files /dev/null and b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..d5f1c8d3 Binary files /dev/null and b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..4d6372ee Binary files /dev/null and b/third_party/convex_flutter/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/third_party/convex_flutter/example/android/app/src/main/res/values-night/styles.xml b/third_party/convex_flutter/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 00000000..06952be7 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/third_party/convex_flutter/example/android/app/src/main/res/values/styles.xml b/third_party/convex_flutter/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..cb1ef880 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/third_party/convex_flutter/example/android/app/src/profile/AndroidManifest.xml b/third_party/convex_flutter/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/third_party/convex_flutter/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/third_party/convex_flutter/example/android/build.gradle.kts b/third_party/convex_flutter/example/android/build.gradle.kts new file mode 100644 index 00000000..89176ef4 --- /dev/null +++ b/third_party/convex_flutter/example/android/build.gradle.kts @@ -0,0 +1,21 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/third_party/convex_flutter/example/android/gradle.properties b/third_party/convex_flutter/example/android/gradle.properties new file mode 100644 index 00000000..f018a618 --- /dev/null +++ b/third_party/convex_flutter/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError +android.useAndroidX=true +android.enableJetifier=true diff --git a/third_party/convex_flutter/example/android/gradle/wrapper/gradle-wrapper.properties b/third_party/convex_flutter/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..ac3b4792 --- /dev/null +++ b/third_party/convex_flutter/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip diff --git a/third_party/convex_flutter/example/android/settings.gradle.kts b/third_party/convex_flutter/example/android/settings.gradle.kts new file mode 100644 index 00000000..ab39a10a --- /dev/null +++ b/third_party/convex_flutter/example/android/settings.gradle.kts @@ -0,0 +1,25 @@ +pluginManagement { + val flutterSdkPath = run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "8.7.3" apply false + id("org.jetbrains.kotlin.android") version "2.1.0" apply false +} + +include(":app") diff --git a/third_party/convex_flutter/example/integration_test/simple_test.dart b/third_party/convex_flutter/example/integration_test/simple_test.dart new file mode 100644 index 00000000..f7e577bd --- /dev/null +++ b/third_party/convex_flutter/example/integration_test/simple_test.dart @@ -0,0 +1,11 @@ +import 'package:integration_test/integration_test.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + setUpAll(() async => await RustLib.init()); + test('Can call rust function', () async { + // expect(greet(name: "Tom"), "Hello, Tom!"); + }); +} diff --git a/third_party/convex_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/third_party/convex_flutter/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..1dc6cf76 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 13.0 + + diff --git a/third_party/convex_flutter/example/ios/Flutter/Debug.xcconfig b/third_party/convex_flutter/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..ec97fc6f --- /dev/null +++ b/third_party/convex_flutter/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/third_party/convex_flutter/example/ios/Flutter/Release.xcconfig b/third_party/convex_flutter/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..c4855bfe --- /dev/null +++ b/third_party/convex_flutter/example/ios/Flutter/Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/third_party/convex_flutter/example/ios/Podfile b/third_party/convex_flutter/example/ios/Podfile new file mode 100644 index 00000000..620e46eb --- /dev/null +++ b/third_party/convex_flutter/example/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/third_party/convex_flutter/example/ios/Podfile.lock b/third_party/convex_flutter/example/ios/Podfile.lock new file mode 100644 index 00000000..9eaa971c --- /dev/null +++ b/third_party/convex_flutter/example/ios/Podfile.lock @@ -0,0 +1,28 @@ +PODS: + - convex_flutter (0.0.1): + - Flutter + - Flutter (1.0.0) + - integration_test (0.0.1): + - Flutter + +DEPENDENCIES: + - convex_flutter (from `.symlinks/plugins/convex_flutter/ios`) + - Flutter (from `Flutter`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) + +EXTERNAL SOURCES: + convex_flutter: + :path: ".symlinks/plugins/convex_flutter/ios" + Flutter: + :path: Flutter + integration_test: + :path: ".symlinks/plugins/integration_test/ios" + +SPEC CHECKSUMS: + convex_flutter: 8581c72fdb31ffdbfef9908f23f21668035a032a + Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 + integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e + +PODFILE CHECKSUM: 3c63482e143d1b91d2d2560aee9fb04ecc74ac7e + +COCOAPODS: 1.16.2 diff --git a/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..2142bb7f --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,731 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; + B87EC92B90E5D1D4236EB5F4 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A4045BB261C3A094CFA44EC /* Pods_Runner.framework */; }; + EDD20FBEB619D5A514A1C611 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EF8F3F6DDE398E0F903BED73 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 18C621CFA8A07C85C53E155A /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 1A4045BB261C3A094CFA44EC /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 3E744F493A5CC5201BAB58EE /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 4FE5B1E9D09AB9B4D3603DDC /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8FFBD8D853998A8FDAFE9693 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + AB5BA5233F77CB829DB7164C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + DD93ACA1C1F41A19BD464B8A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + EF8F3F6DDE398E0F903BED73 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 7BF3221F20726CC94DF5C3AC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + EDD20FBEB619D5A514A1C611 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + B87EC92B90E5D1D4236EB5F4 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C8082294A63A400263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C807B294A618700263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 59DCBF4450BCBEF58AD02F1C /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1A4045BB261C3A094CFA44EC /* Pods_Runner.framework */, + EF8F3F6DDE398E0F903BED73 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 9DC8167AFFCDEA8CCC7DD883 /* Pods */, + 59DCBF4450BCBEF58AD02F1C /* Frameworks */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; + 9DC8167AFFCDEA8CCC7DD883 /* Pods */ = { + isa = PBXGroup; + children = ( + 3E744F493A5CC5201BAB58EE /* Pods-Runner.debug.xcconfig */, + 8FFBD8D853998A8FDAFE9693 /* Pods-Runner.release.xcconfig */, + 4FE5B1E9D09AB9B4D3603DDC /* Pods-Runner.profile.xcconfig */, + 18C621CFA8A07C85C53E155A /* Pods-RunnerTests.debug.xcconfig */, + AB5BA5233F77CB829DB7164C /* Pods-RunnerTests.release.xcconfig */, + DD93ACA1C1F41A19BD464B8A /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + F1C5B2B1C58C5433761D2FB8 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + 7BF3221F20726CC94DF5C3AC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + FCB70B0446B240AB6783B5A6 /* [CP] Check Pods Manifest.lock */, + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 79F53A30267DAF502589689D /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 79F53A30267DAF502589689D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; + F1C5B2B1C58C5433761D2FB8 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + FCB70B0446B240AB6783B5A6 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A2N5J9H9QJ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 18C621CFA8A07C85C53E155A /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AB5BA5233F77CB829DB7164C /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = DD93ACA1C1F41A19BD464B8A /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A2N5J9H9QJ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = A2N5J9H9QJ; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/third_party/convex_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..e3773d42 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/third_party/convex_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/third_party/convex_flutter/example/ios/Runner/AppDelegate.swift b/third_party/convex_flutter/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..62666446 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Flutter +import UIKit + +@main +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 00000000..dc9ada47 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png new file mode 100644 index 00000000..7353c41e Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 00000000..6ed2d933 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png new file mode 100644 index 00000000..4cd7b009 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 00000000..fe730945 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png new file mode 100644 index 00000000..321773cd Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png new file mode 100644 index 00000000..797d452e Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 00000000..502f463a Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 00000000..0ec30343 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 00000000..e9f5fea2 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 00000000..84ac32ae Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png new file mode 100644 index 00000000..8953cba0 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png new file mode 100644 index 00000000..0467bf12 Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 00000000..0bedcf2f --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 00000000..9da19eac Binary files /dev/null and b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/third_party/convex_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/third_party/convex_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/ios/Runner/Base.lproj/Main.storyboard b/third_party/convex_flutter/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/ios/Runner/Info.plist b/third_party/convex_flutter/example/ios/Runner/Info.plist new file mode 100644 index 00000000..a8459efc --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Convex Flutter + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + convex_flutter_example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSupportsIndirectInputEvents + + + diff --git a/third_party/convex_flutter/example/ios/Runner/Runner-Bridging-Header.h b/third_party/convex_flutter/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/third_party/convex_flutter/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/third_party/convex_flutter/example/ios/RunnerTests/RunnerTests.swift b/third_party/convex_flutter/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/third_party/convex_flutter/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/third_party/convex_flutter/example/lib/main.dart b/third_party/convex_flutter/example/lib/main.dart new file mode 100644 index 00000000..ace9715b --- /dev/null +++ b/third_party/convex_flutter/example/lib/main.dart @@ -0,0 +1,210 @@ +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; +import 'screens/home_screen.dart'; +import 'screens/authentication_screen.dart'; +import 'screens/messaging_screen.dart'; +import 'screens/connection_screen.dart'; +import 'screens/advanced_screen.dart'; +import 'widgets/connection_status_indicator.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: "https://merry-grasshopper-563.convex.cloud", + clientId: "flutter-app-1.0", + operationTimeout: const Duration(seconds: 30), + healthCheckQuery: "health:ping", + ), + ); + + runApp(const ConvexExampleApp()); +} + +class ConvexExampleApp extends StatelessWidget { + const ConvexExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Convex Flutter Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + useMaterial3: true, + ), + home: const MainNavigationScreen(), + ); + } +} + +class MainNavigationScreen extends StatefulWidget { + const MainNavigationScreen({super.key}); + + @override + State createState() => _MainNavigationScreenState(); +} + +class _MainNavigationScreenState extends State { + int _selectedIndex = 0; + + final List _screens = const [ + HomeScreen(), + AuthenticationScreen(), + MessagingScreen(), + ConnectionScreen(), + AdvancedScreen(), + ]; + + final List _navItems = const [ + NavigationItem( + icon: Icons.home, + label: 'Home', + description: 'Welcome and overview', + ), + NavigationItem( + icon: Icons.login, + label: 'Authentication', + description: 'JWT tokens, auto-refresh, auth state', + ), + NavigationItem( + icon: Icons.message, + label: 'Messaging', + description: 'Query, mutation, subscribe, live updates', + ), + NavigationItem( + icon: Icons.wifi, + label: 'Connection', + description: 'WebSocket state, health checks, reconnect', + ), + NavigationItem( + icon: Icons.settings, + label: 'Advanced', + description: 'Timeouts, actions, error handling, lifecycle', + ), + ]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_navItems[_selectedIndex].label), + actions: const [ + ConnectionStatusIndicator(), + ], + ), + drawer: _buildDrawer(), + body: _screens[_selectedIndex], + ); + } + + Widget _buildDrawer() { + return Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + DrawerHeader( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + Theme.of(context).colorScheme.primary, + Theme.of(context).colorScheme.primaryContainer, + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Icon(Icons.cloud, color: Colors.white, size: 48), + const SizedBox(height: 8), + const Text( + 'Convex Flutter', + style: TextStyle(color: Colors.white, fontSize: 24, + fontWeight: FontWeight.bold), + ), + const Text( + 'Example App', + style: TextStyle(color: Colors.white70, fontSize: 16), + ), + const Spacer(), + // Auth state indicator in drawer + StreamBuilder( + stream: ConvexClient.instance.authState, + initialData: false, + builder: (context, snapshot) { + final isAuth = snapshot.data ?? false; + return Row( + children: [ + Icon( + isAuth ? Icons.verified_user : Icons.person_off, + color: Colors.white70, + size: 16, + ), + const SizedBox(width: 4), + Text( + isAuth ? 'Authenticated' : 'Not Authenticated', + style: const TextStyle(color: Colors.white70, fontSize: 12), + ), + ], + ); + }, + ), + ], + ), + ), + ...List.generate(_navItems.length, (index) { + final item = _navItems[index]; + return ListTile( + leading: Icon(item.icon), + title: Text(item.label), + subtitle: Text(item.description, style: const TextStyle(fontSize: 12)), + selected: _selectedIndex == index, + selectedTileColor: Theme.of(context).colorScheme.primaryContainer, + onTap: () { + setState(() => _selectedIndex = index); + Navigator.pop(context); + }, + ); + }), + const Divider(), + ListTile( + leading: const Icon(Icons.info_outline), + title: const Text('About'), + subtitle: const Text('Convex Flutter SDK v2.0.0', style: TextStyle(fontSize: 12)), + onTap: () { + Navigator.pop(context); + showAboutDialog( + context: context, + applicationName: 'Convex Flutter', + applicationVersion: '2.0.0', + applicationIcon: const Icon(Icons.cloud, size: 48), + children: const [ + Text('Comprehensive example app demonstrating all features of the Convex Flutter SDK.'), + SizedBox(height: 8), + Text('Features:\n' + '• Real-time subscriptions\n' + '• Authentication & token refresh\n' + '• WebSocket connection state\n' + '• Query, mutation, and action support\n' + '• Lifecycle management'), + ], + ); + }, + ), + ], + ), + ); + } +} + +class NavigationItem { + final IconData icon; + final String label; + final String description; + + const NavigationItem({ + required this.icon, + required this.label, + required this.description, + }); +} diff --git a/third_party/convex_flutter/example/lib/screens/advanced_screen.dart b/third_party/convex_flutter/example/lib/screens/advanced_screen.dart new file mode 100644 index 00000000..057ee594 --- /dev/null +++ b/third_party/convex_flutter/example/lib/screens/advanced_screen.dart @@ -0,0 +1,225 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; +import 'package:convex_flutter/convex_flutter.dart'; + +class AdvancedScreen extends StatefulWidget { + const AdvancedScreen({super.key}); + + @override + State createState() => _AdvancedScreenState(); +} + +class _AdvancedScreenState extends State { + String? _timeoutResult; + bool _isTesting = false; + AppLifecycleEvent? _currentLifecycle; + final List _lifecycleHistory = []; + + @override + void initState() { + super.initState(); + ConvexClient.instance.lifecycleEvents.listen((event) { + setState(() { + _currentLifecycle = event; + _lifecycleHistory.insert(0, '${DateTime.now()}: ${event.name}'); + if (_lifecycleHistory.length > 10) _lifecycleHistory.removeLast(); + }); + }); + } + + Future _testTimeout(int seconds) async { + setState(() { + _isTesting = true; + _timeoutResult = null; + }); + + final stopwatch = Stopwatch()..start(); + try { + await ConvexClient.instance.query("messages:list", {}); + stopwatch.stop(); + setState(() { + _timeoutResult = 'Success in ${stopwatch.elapsedMilliseconds}ms'; + _isTesting = false; + }); + } on TimeoutException { + stopwatch.stop(); + setState(() { + _timeoutResult = 'Timeout after ${stopwatch.elapsedMilliseconds}ms'; + _isTesting = false; + }); + } catch (e) { + stopwatch.stop(); + setState(() { + _timeoutResult = 'Error: $e (${stopwatch.elapsedMilliseconds}ms)'; + _isTesting = false; + }); + } + } + + Future _testAction() async { + try { + final result = await ConvexClient.instance.action( + name: "myActions:doSomething", + args: {"param": "test"}, + ); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Action result: $result'))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Action failed: $e'))); + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Timeout testing + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Timeout Testing', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + const Text('Test query execution with different timeouts', + style: TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 12), + Wrap( + spacing: 8, + children: [ + ElevatedButton( + onPressed: _isTesting ? null : () => _testTimeout(1), + child: const Text('1s timeout')), + ElevatedButton( + onPressed: _isTesting ? null : () => _testTimeout(5), + child: const Text('5s timeout')), + ElevatedButton( + onPressed: _isTesting ? null : () => _testTimeout(30), + child: const Text('30s timeout')), + ], + ), + if (_timeoutResult != null) ...[ + const SizedBox(height: 12), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.grey.shade100, + borderRadius: BorderRadius.circular(4)), + child: Text(_timeoutResult!, + style: const TextStyle(fontFamily: 'monospace')), + ), + ], + if (_isTesting) + const Padding( + padding: EdgeInsets.only(top: 12), + child: LinearProgressIndicator()), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Actions + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Server Actions', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + const Text('Execute backend actions (long-running operations)', + style: TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _testAction, + icon: const Icon(Icons.play_arrow), + label: const Text('Run Test Action')), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Lifecycle management + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('App Lifecycle', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + Row( + children: [ + const Text('Current State:', + style: TextStyle(fontWeight: FontWeight.w500)), + const SizedBox(width: 8), + Chip( + label: Text(_currentLifecycle?.name ?? 'unknown'), + backgroundColor: _currentLifecycle == AppLifecycleEvent.resumed + ? Colors.green.shade100 + : Colors.grey.shade100), + ], + ), + const SizedBox(height: 12), + const Text('Recent Events:', + style: TextStyle(fontWeight: FontWeight.w500)), + const SizedBox(height: 8), + if (_lifecycleHistory.isEmpty) + const Text('No events yet', style: TextStyle(color: Colors.grey)) + else + ...(_lifecycleHistory.map((event) => Padding( + padding: const EdgeInsets.only(bottom: 4), + child: Text(event, + style: const TextStyle(fontSize: 12, fontFamily: 'monospace')), + ))), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Error handling examples + Card( + color: Colors.orange.shade50, + child: const Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon(Icons.lightbulb_outline, color: Colors.orange), + SizedBox(width: 8), + Text('Error Handling Tips', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + ], + ), + SizedBox(height: 12), + Text('• Always wrap operations in try-catch\n' + '• Handle TimeoutException separately\n' + '• Check ClientError types for specifics\n' + '• Use subscription onError callbacks\n' + '• Monitor connection state changes', + style: TextStyle(fontSize: 13)), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/third_party/convex_flutter/example/lib/screens/authentication_screen.dart b/third_party/convex_flutter/example/lib/screens/authentication_screen.dart new file mode 100644 index 00000000..5187a891 --- /dev/null +++ b/third_party/convex_flutter/example/lib/screens/authentication_screen.dart @@ -0,0 +1,224 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +class AuthenticationScreen extends StatefulWidget { + const AuthenticationScreen({super.key}); + + @override + State createState() => _AuthenticationScreenState(); +} + +class _AuthenticationScreenState extends State { + final TextEditingController _tokenController = TextEditingController(); + bool _isAuthenticated = false; + AuthHandleWrapper? _authHandle; + int _refreshCount = 0; + DateTime? _lastRefreshTime; + Map? _tokenClaims; + + @override + void initState() { + super.initState(); + ConvexClient.instance.authState.listen((isAuth) { + setState(() => _isAuthenticated = isAuth); + }); + } + + @override + void dispose() { + _tokenController.dispose(); + _authHandle?.dispose(); + super.dispose(); + } + + // Decode JWT to show claims + void _decodeToken(String token) { + try { + final parts = token.split('.'); + if (parts.length != 3) return; + final payload = utf8.decode(base64Url.decode(base64Url.normalize(parts[1]))); + setState(() => _tokenClaims = jsonDecode(payload)); + } catch (e) { + debugPrint('Error decoding token: $e'); + } + } + + Future _setAuth() async { + final token = _tokenController.text.trim(); + if (token.isEmpty) return; + + try { + await ConvexClient.instance.setAuth(token: token); + _decodeToken(token); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Auth token set successfully'))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e'))); + } + } + + Future _setAuthWithRefresh() async { + try { + _authHandle?.dispose(); + _authHandle = await ConvexClient.instance.setAuthWithRefresh( + fetchToken: () async { + setState(() { + _refreshCount++; + _lastRefreshTime = DateTime.now(); + }); + // Mock token generation for demo + return 'mock_token_${DateTime.now().millisecondsSinceEpoch}'; + }, + onAuthChange: (isAuthenticated) { + debugPrint('Auth changed: $isAuthenticated'); + }, + ); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Auto-refresh enabled'))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e'))); + } + } + + Future _clearAuth() async { + try { + _authHandle?.dispose(); + _authHandle = null; + await ConvexClient.instance.clearAuth(); + setState(() { + _tokenClaims = null; + _refreshCount = 0; + _lastRefreshTime = null; + }); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Auth cleared'))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $e'))); + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Auth status card + Card( + color: _isAuthenticated ? Colors.green.shade50 : Colors.grey.shade100, + child: Padding( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + Icon(_isAuthenticated ? Icons.verified_user : Icons.person_off, + color: _isAuthenticated ? Colors.green : Colors.grey, size: 32), + const SizedBox(width: 12), + Text(_isAuthenticated ? 'Authenticated' : 'Not Authenticated', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Static token auth + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Static Token Auth', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 12), + TextField( + controller: _tokenController, + decoration: const InputDecoration( + labelText: 'JWT Token', + border: OutlineInputBorder(), + hintText: 'Paste your JWT token here'), + ), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _setAuth, + icon: const Icon(Icons.login), + label: const Text('Set Auth Token'), + ), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Auto-refresh auth + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Auto-Refresh Auth (Recommended)', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + const Text('Automatically refreshes tokens before expiry', + style: TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 12), + ElevatedButton.icon( + onPressed: _setAuthWithRefresh, + icon: const Icon(Icons.autorenew), + label: const Text('Enable Auto-Refresh'), + ), + if (_refreshCount > 0) ...[ + const SizedBox(height: 12), + Text('Refresh count: $_refreshCount', + style: const TextStyle(fontFamily: 'monospace')), + if (_lastRefreshTime != null) + Text('Last refresh: ${_lastRefreshTime}', + style: const TextStyle(fontSize: 12)), + ], + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Token info + if (_tokenClaims != null) + Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Token Claims', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Text(jsonEncode(_tokenClaims), + style: const TextStyle(fontFamily: 'monospace', fontSize: 12)), + ], + ), + ), + ), + + const SizedBox(height: 16), + + // Clear auth + OutlinedButton.icon( + onPressed: _clearAuth, + icon: const Icon(Icons.logout), + label: const Text('Clear Auth'), + style: OutlinedButton.styleFrom(foregroundColor: Colors.red), + ), + ], + ), + ); + } +} diff --git a/third_party/convex_flutter/example/lib/screens/connection_screen.dart b/third_party/convex_flutter/example/lib/screens/connection_screen.dart new file mode 100644 index 00000000..4bf06e5b --- /dev/null +++ b/third_party/convex_flutter/example/lib/screens/connection_screen.dart @@ -0,0 +1,199 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +class ConnectionScreen extends StatefulWidget { + const ConnectionScreen({super.key}); + + @override + State createState() => _ConnectionScreenState(); +} + +class _ConnectionScreenState extends State { + final List _stateHistory = []; + StreamSubscription? _connectionSubscription; + + @override + void initState() { + super.initState(); + _connectionSubscription = ConvexClient.instance.connectionState.listen((state) { + if (mounted) { + setState(() { + _stateHistory.insert(0, ConnectionEvent( + state: state, + timestamp: DateTime.now())); + if (_stateHistory.length > 20) _stateHistory.removeLast(); + }); + } + }); + } + + @override + void dispose() { + _connectionSubscription?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCurrentStateCard(), + const SizedBox(height: 16), + _buildFeatureCard(), + const SizedBox(height: 16), + _buildHistoryCard(), + ], + ), + ); + } + + Widget _buildCurrentStateCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('WebSocket Connection State', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + const Text('Real-time state from underlying WebSocket', + style: TextStyle(fontSize: 12, color: Colors.grey)), + const SizedBox(height: 16), + StreamBuilder( + stream: ConvexClient.instance.connectionState, + initialData: ConvexClient.instance.currentConnectionState, + builder: (context, snapshot) { + final state = snapshot.data!; + final isConnected = state == WebSocketConnectionState.connected; + return Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: isConnected ? Colors.green.shade50 : Colors.orange.shade50, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isConnected ? Colors.green : Colors.orange, width: 2)), + child: Row( + children: [ + Icon(isConnected ? Icons.cloud_done : Icons.cloud_sync, + color: isConnected ? Colors.green : Colors.orange, size: 48), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(state.name.toUpperCase(), + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, + color: isConnected ? Colors.green : Colors.orange)), + Text(isConnected ? 'WebSocket is open' : 'WebSocket connecting', + style: const TextStyle(fontSize: 12)), + ], + ), + ), + ], + ), + ); + }), + const SizedBox(height: 12), + Text('isConnected: ${ConvexClient.instance.isConnected}', + style: const TextStyle(fontFamily: 'monospace', fontSize: 12)), + ], + ), + ), + ); + } + + Widget _buildFeatureCard() { + return Card( + color: Colors.blue.shade50, + child: const Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('✨ New Feature: Real-time Connection State', + style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), + SizedBox(height: 8), + Text('• Automatic state updates without polling\n' + '• Two states: Connected and Connecting\n' + '• Reflects actual WebSocket connection\n' + '• Access via connectionState stream\n' + '• Convenience getter: isConnected', + style: TextStyle(fontSize: 13)), + ], + ), + ), + ); + } + + Widget _buildHistoryCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('State Change History', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + TextButton.icon( + onPressed: () => setState(() => _stateHistory.clear()), + icon: const Icon(Icons.clear_all, size: 16), + label: const Text('Clear')), + ], + ), + const SizedBox(height: 8), + if (_stateHistory.isEmpty) + const Padding( + padding: EdgeInsets.all(16), + child: Center(child: Text('No state changes yet', + style: TextStyle(color: Colors.grey)))) + else + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _stateHistory.length, + separatorBuilder: (_, __) => const Divider(), + itemBuilder: (context, index) { + final event = _stateHistory[index]; + final isConnected = event.state == WebSocketConnectionState.connected; + return ListTile( + leading: Icon(isConnected ? Icons.cloud_done : Icons.cloud_sync, + color: isConnected ? Colors.green : Colors.orange), + title: Text(event.state.name.toUpperCase()), + subtitle: Text(_formatTime(event.timestamp)), + trailing: Text(_timeAgo(event.timestamp), + style: const TextStyle(fontSize: 11, color: Colors.grey)), + ); + }, + ), + ], + ), + ), + ); + } + + String _formatTime(DateTime dt) { + return '${dt.hour.toString().padLeft(2, '0')}:' + '${dt.minute.toString().padLeft(2, '0')}:' + '${dt.second.toString().padLeft(2, '0')}'; + } + + String _timeAgo(DateTime dt) { + final diff = DateTime.now().difference(dt); + if (diff.inSeconds < 60) return '${diff.inSeconds}s ago'; + if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; + return '${diff.inHours}h ago'; + } +} + +class ConnectionEvent { + final WebSocketConnectionState state; + final DateTime timestamp; + ConnectionEvent({required this.state, required this.timestamp}); +} diff --git a/third_party/convex_flutter/example/lib/screens/home_screen.dart b/third_party/convex_flutter/example/lib/screens/home_screen.dart new file mode 100644 index 00000000..1612dea2 --- /dev/null +++ b/third_party/convex_flutter/example/lib/screens/home_screen.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +class HomeScreen extends StatefulWidget { + const HomeScreen({super.key}); + + @override + State createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + @override + void initState() { + super.initState(); + // Trigger auto-connection on app startup + _establishConnection(); + } + + Future _establishConnection() async { + try { + // Use a health check query to establish the WebSocket connection + // Create this query in your Convex backend: convex/health.ts + await ConvexClient.instance.query( + 'health:ping', + {}, + ); + debugPrint('HomeScreen: Auto-connection established via health check query'); + } catch (e) { + debugPrint('HomeScreen: Auto-connection failed: $e'); + // Connection will retry automatically via Convex client + } + } + + @override + Widget build(BuildContext context) { + return SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Card( + child: Padding( + padding: const EdgeInsets.all(24), + child: Column( + children: [ + Icon(Icons.rocket_launch, size: 64, + color: Theme.of(context).colorScheme.primary), + const SizedBox(height: 16), + Text('Welcome to Convex Flutter', + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center), + const SizedBox(height: 8), + const Text( + 'Explore all SDK capabilities', + textAlign: TextAlign.center, + style: TextStyle(color: Colors.grey)), + ], + ), + ), + ), + const SizedBox(height: 24), + Text('Features', style: Theme.of(context).textTheme.titleLarge), + const SizedBox(height: 16), + _FeatureCard(icon: Icons.login, title: 'Authentication', + description: 'JWT tokens, auto-refresh',color: Colors.purple), + _FeatureCard(icon: Icons.message, title: 'Real-time Messaging', + description: 'Subscriptions, queries, mutations', color: Colors.blue), + _FeatureCard(icon: Icons.wifi, title: 'Connection State', + description: 'WebSocket state tracking', color: Colors.green), + _FeatureCard(icon: Icons.settings, title: 'Advanced', + description: 'Timeouts, actions, lifecycle', color: Colors.orange), + const SizedBox(height: 24), + _buildStatusSummary(), + ], + ), + ); + } + + Widget _buildStatusSummary() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text('Current Status', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)), + const SizedBox(height: 12), + StreamBuilder( + stream: ConvexClient.instance.connectionState, + builder: (context, snapshot) { + final state = snapshot.data; + return _StatusRow(icon: Icons.wifi, label: 'Connection', + value: state?.name ?? 'Unknown', + color: state == WebSocketConnectionState.connected + ? Colors.green : Colors.orange); + }), + const Divider(), + StreamBuilder( + stream: ConvexClient.instance.authState, + builder: (context, snapshot) { + final isAuth = snapshot.data ?? false; + return _StatusRow(icon: Icons.lock, label: 'Auth', + value: isAuth ? 'Yes' : 'No', + color: isAuth ? Colors.green : Colors.grey); + }), + ], + ), + ), + ); + } +} + +class _FeatureCard extends StatelessWidget { + final IconData icon; + final String title; + final String description; + final Color color; + const _FeatureCard({required this.icon, required this.title, + required this.description, required this.color}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.only(bottom: 12), + child: ListTile( + leading: Icon(icon, color: color, size: 32), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.bold)), + subtitle: Text(description), + ), + ); + } +} + +class _StatusRow extends StatelessWidget { + final IconData icon; + final String label; + final String value; + final Color color; + const _StatusRow({required this.icon, required this.label, + required this.value, required this.color}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Icon(icon, size: 20, color: color), + const SizedBox(width: 12), + Text(label, style: const TextStyle(fontWeight: FontWeight.w500)), + const Spacer(), + Text(value, style: TextStyle(color: color)), + ], + ); + } +} diff --git a/third_party/convex_flutter/example/lib/screens/messaging_screen.dart b/third_party/convex_flutter/example/lib/screens/messaging_screen.dart new file mode 100644 index 00000000..757bd2cd --- /dev/null +++ b/third_party/convex_flutter/example/lib/screens/messaging_screen.dart @@ -0,0 +1,206 @@ +import 'dart:convert'; +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +class MessagingScreen extends StatefulWidget { + const MessagingScreen({super.key}); + + @override + State createState() => _MessagingScreenState(); +} + +class _MessagingScreenState extends State { + final TextEditingController _messageController = TextEditingController(); + final String _currentUserId = "Flutter App"; + List> _messages = []; + SubscriptionHandle? _subscriptionHandle; + bool _isSubscribed = false; + int _messageCount = 0; + + @override + void initState() { + super.initState(); + _startSubscription(); + } + + @override + void dispose() { + _messageController.dispose(); + _subscriptionHandle?.cancel(); + super.dispose(); + } + + Future _startSubscription() async { + if (_subscriptionHandle != null) return; + + try { + _subscriptionHandle = await ConvexClient.instance.subscribe( + name: "messages:list", + args: {}, + onUpdate: (value) { + if (!mounted) return; + final List jsonList = jsonDecode(value); + final List> parsedMessages = + jsonList.map((e) => e as Map).toList(); + setState(() { + _messages = parsedMessages; + _messageCount = parsedMessages.length; + _isSubscribed = true; + }); + }, + onError: (message, value) { + if (!mounted) return; + debugPrint("Subscription error: $message"); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Error: $message'))); + }, + ); + if (mounted) setState(() => _isSubscribed = true); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to subscribe: $e'))); + } + } + + void _stopSubscription() { + _subscriptionHandle?.cancel(); + _subscriptionHandle = null; + setState(() { + _isSubscribed = false; + _messages.clear(); + }); + } + + Future _sendMessage() async { + final message = _messageController.text.trim(); + if (message.isEmpty) return; + + try { + await ConvexClient.instance.mutation( + name: "messages:send", + args: {"body": message, "author": _currentUserId}, + ); + _messageController.clear(); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to send: $e'))); + } + } + + Future _queryMessages() async { + try { + final result = await ConvexClient.instance.query("messages:list", {}); + final List jsonList = jsonDecode(result); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Query returned ${jsonList.length} messages'))); + } catch (e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Query failed: $e'))); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + // Status bar + Container( + padding: const EdgeInsets.all(12), + color: _isSubscribed ? Colors.green.shade100 : Colors.orange.shade100, + child: Row( + children: [ + Icon(_isSubscribed ? Icons.wifi : Icons.wifi_off, + color: _isSubscribed ? Colors.green : Colors.orange), + const SizedBox(width: 8), + Text(_isSubscribed ? 'Live Updates' : 'Paused', + style: const TextStyle(fontWeight: FontWeight.bold)), + const Spacer(), + Text('$_messageCount messages'), + const SizedBox(width: 8), + IconButton( + icon: Icon(_isSubscribed ? Icons.pause : Icons.play_arrow), + onPressed: _isSubscribed ? _stopSubscription : _startSubscription, + ), + IconButton( + icon: const Icon(Icons.refresh), + onPressed: _queryMessages, + tooltip: 'Query messages'), + ], + ), + ), + + // Message list + Expanded( + child: _messages.isEmpty + ? const Center(child: Text('No messages yet')) + : ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: _messages.length, + itemBuilder: (context, index) { + final message = _messages[index]; + final isMyMessage = message['userId'] == _currentUserId || + message['author'] == _currentUserId; + + return Align( + alignment: isMyMessage + ? Alignment.centerRight + : Alignment.centerLeft, + child: Container( + margin: const EdgeInsets.only(bottom: 8), + padding: const EdgeInsets.all(12), + constraints: BoxConstraints( + maxWidth: MediaQuery.of(context).size.width * 0.7), + decoration: BoxDecoration( + color: isMyMessage ? Colors.blue[100] : Colors.grey[200], + borderRadius: BorderRadius.circular(12).copyWith( + bottomRight: isMyMessage ? Radius.zero : const Radius.circular(12), + bottomLeft: isMyMessage ? const Radius.circular(12) : Radius.zero), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(message['body'] ?? '', + style: const TextStyle(fontSize: 16)), + if (message['author'] != null) + Text('- ${message['author']}', + style: const TextStyle(fontSize: 10, color: Colors.grey)), + ], + ), + ), + ); + }, + ), + ), + + // Input area + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Colors.white, + boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.2), + spreadRadius: 1, blurRadius: 3)], + ), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _messageController, + decoration: InputDecoration( + hintText: 'Type a message...', + border: OutlineInputBorder(borderRadius: BorderRadius.circular(24)), + contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8)), + onSubmitted: (_) => _sendMessage(), + ), + ), + const SizedBox(width: 8), + IconButton( + onPressed: _sendMessage, + icon: const Icon(Icons.send), + color: Colors.blue), + ], + ), + ), + ], + ); + } +} diff --git a/third_party/convex_flutter/example/lib/widgets/connection_status_indicator.dart b/third_party/convex_flutter/example/lib/widgets/connection_status_indicator.dart new file mode 100644 index 00000000..6acdd79a --- /dev/null +++ b/third_party/convex_flutter/example/lib/widgets/connection_status_indicator.dart @@ -0,0 +1,66 @@ +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; + +/// A reusable widget that displays the current WebSocket connection state. +/// +/// This widget listens to the real-time connection state stream and +/// displays a colored chip indicator in the app bar. +class ConnectionStatusIndicator extends StatelessWidget { + const ConnectionStatusIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: ConvexClient.instance.connectionState, + initialData: ConvexClient.instance.currentConnectionState, + builder: (context, snapshot) { + print('snapshot: ${snapshot.data}'); + final state = snapshot.data ?? WebSocketConnectionState.connecting; + + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 12), + child: Chip( + avatar: Icon( + _getIcon(state), + size: 16, + color: _getColor(state), + ), + label: Text( + _getLabel(state), + style: const TextStyle(fontSize: 11), + ), + backgroundColor: Colors.white.withOpacity(0.9), + padding: const EdgeInsets.symmetric(horizontal: 4), + ), + ); + }, + ); + } + + IconData _getIcon(WebSocketConnectionState state) { + switch (state) { + case WebSocketConnectionState.connected: + return Icons.cloud_done; + case WebSocketConnectionState.connecting: + return Icons.cloud_sync; + } + } + + Color _getColor(WebSocketConnectionState state) { + switch (state) { + case WebSocketConnectionState.connected: + return Colors.green; + case WebSocketConnectionState.connecting: + return Colors.orange; + } + } + + String _getLabel(WebSocketConnectionState state) { + switch (state) { + case WebSocketConnectionState.connected: + return 'Connected'; + case WebSocketConnectionState.connecting: + return 'Connecting'; + } + } +} diff --git a/third_party/convex_flutter/example/linux/CMakeLists.txt b/third_party/convex_flutter/example/linux/CMakeLists.txt new file mode 100644 index 00000000..a0288d7b --- /dev/null +++ b/third_party/convex_flutter/example/linux/CMakeLists.txt @@ -0,0 +1,128 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "convex_flutter_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.convex_flutter") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/third_party/convex_flutter/example/linux/flutter/CMakeLists.txt b/third_party/convex_flutter/example/linux/flutter/CMakeLists.txt new file mode 100644 index 00000000..d5bd0164 --- /dev/null +++ b/third_party/convex_flutter/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.cc b/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..e71a16d2 --- /dev/null +++ b/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.h b/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..e0f0a47b --- /dev/null +++ b/third_party/convex_flutter/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/third_party/convex_flutter/example/linux/flutter/generated_plugins.cmake b/third_party/convex_flutter/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 00000000..410e76ed --- /dev/null +++ b/third_party/convex_flutter/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + convex_flutter +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/third_party/convex_flutter/example/linux/runner/CMakeLists.txt b/third_party/convex_flutter/example/linux/runner/CMakeLists.txt new file mode 100644 index 00000000..e97dabc7 --- /dev/null +++ b/third_party/convex_flutter/example/linux/runner/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.13) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the application ID. +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") diff --git a/third_party/convex_flutter/example/linux/runner/main.cc b/third_party/convex_flutter/example/linux/runner/main.cc new file mode 100644 index 00000000..e7c5c543 --- /dev/null +++ b/third_party/convex_flutter/example/linux/runner/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/third_party/convex_flutter/example/linux/runner/my_application.cc b/third_party/convex_flutter/example/linux/runner/my_application.cc new file mode 100644 index 00000000..e73fb97d --- /dev/null +++ b/third_party/convex_flutter/example/linux/runner/my_application.cc @@ -0,0 +1,130 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "convex_flutter_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "convex_flutter_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + // Set the program name to the application ID, which helps various systems + // like GTK and desktop environments map this running application to its + // corresponding .desktop file. This ensures better integration by allowing + // the application to be recognized beyond its binary name. + g_set_prgname(APPLICATION_ID); + + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/third_party/convex_flutter/example/linux/runner/my_application.h b/third_party/convex_flutter/example/linux/runner/my_application.h new file mode 100644 index 00000000..72271d5e --- /dev/null +++ b/third_party/convex_flutter/example/linux/runner/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/third_party/convex_flutter/example/macos/Flutter/Flutter-Debug.xcconfig b/third_party/convex_flutter/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/third_party/convex_flutter/example/macos/Flutter/Flutter-Release.xcconfig b/third_party/convex_flutter/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/third_party/convex_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift b/third_party/convex_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/third_party/convex_flutter/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/third_party/convex_flutter/example/macos/Podfile b/third_party/convex_flutter/example/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/third_party/convex_flutter/example/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/third_party/convex_flutter/example/macos/Podfile.lock b/third_party/convex_flutter/example/macos/Podfile.lock new file mode 100644 index 00000000..29876fc6 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - convex_flutter (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - convex_flutter (from `Flutter/ephemeral/.symlinks/plugins/convex_flutter/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + convex_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/convex_flutter/macos + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + convex_flutter: a9d12846e80c8a2238282775fa9eb9c906efcea4 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.pbxproj b/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..fb7849d4 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 5F83E745364ED6BF9E7FF659 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E71D87C3EB6720BF44F05D8B /* Pods_Runner.framework */; }; + BFFE63166C502BDD7B20AF71 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2557E6C7825F3FC95F3CD0D8 /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 2557E6C7825F3FC95F3CD0D8 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 32099594734A516286FC2264 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* convex_flutter_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = convex_flutter_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 50D06C36722B18DAB0BE360E /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 53F61EED7E54B288A8BC0ABC /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 5571B207020C2E29ECBC07B5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + 9860AB98CAFCFA22A12134B5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + D63BC546645F3C3533E4F378 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + E71D87C3EB6720BF44F05D8B /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BFFE63166C502BDD7B20AF71 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5F83E745364ED6BF9E7FF659 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 579DD7398EE076E17B680A75 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* convex_flutter_example.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 579DD7398EE076E17B680A75 /* Pods */ = { + isa = PBXGroup; + children = ( + 53F61EED7E54B288A8BC0ABC /* Pods-Runner.debug.xcconfig */, + 5571B207020C2E29ECBC07B5 /* Pods-Runner.release.xcconfig */, + 50D06C36722B18DAB0BE360E /* Pods-Runner.profile.xcconfig */, + 32099594734A516286FC2264 /* Pods-RunnerTests.debug.xcconfig */, + 9860AB98CAFCFA22A12134B5 /* Pods-RunnerTests.release.xcconfig */, + D63BC546645F3C3533E4F378 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + E71D87C3EB6720BF44F05D8B /* Pods_Runner.framework */, + 2557E6C7825F3FC95F3CD0D8 /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 4FE9D07133D15C36BF94B9EC /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9B884211EDE4031D884FE3A3 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 4B187D1D2999DC2E01B75E0C /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* convex_flutter_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 4B187D1D2999DC2E01B75E0C /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 4FE9D07133D15C36BF94B9EC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 9B884211EDE4031D884FE3A3 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 32099594734A516286FC2264 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/convex_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/convex_flutter_example"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9860AB98CAFCFA22A12134B5 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/convex_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/convex_flutter_example"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D63BC546645F3C3533E4F378 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/convex_flutter_example.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/convex_flutter_example"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/third_party/convex_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/third_party/convex_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..6266fa98 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/third_party/convex_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/third_party/convex_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/third_party/convex_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/third_party/convex_flutter/example/macos/Runner/AppDelegate.swift b/third_party/convex_flutter/example/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/third_party/convex_flutter/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/third_party/convex_flutter/example/macos/Runner/Base.lproj/MainMenu.xib b/third_party/convex_flutter/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/third_party/convex_flutter/example/macos/Runner/Configs/AppInfo.xcconfig b/third_party/convex_flutter/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..0110a435 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = convex_flutter_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.convexFlutterExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved. diff --git a/third_party/convex_flutter/example/macos/Runner/Configs/Debug.xcconfig b/third_party/convex_flutter/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/third_party/convex_flutter/example/macos/Runner/Configs/Release.xcconfig b/third_party/convex_flutter/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/third_party/convex_flutter/example/macos/Runner/Configs/Warnings.xcconfig b/third_party/convex_flutter/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/third_party/convex_flutter/example/macos/Runner/DebugProfile.entitlements b/third_party/convex_flutter/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..08c3ab17 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/third_party/convex_flutter/example/macos/Runner/Info.plist b/third_party/convex_flutter/example/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/third_party/convex_flutter/example/macos/Runner/MainFlutterWindow.swift b/third_party/convex_flutter/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/third_party/convex_flutter/example/macos/Runner/Release.entitlements b/third_party/convex_flutter/example/macos/Runner/Release.entitlements new file mode 100644 index 00000000..64cabb4e --- /dev/null +++ b/third_party/convex_flutter/example/macos/Runner/Release.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/third_party/convex_flutter/example/macos/RunnerTests/RunnerTests.swift b/third_party/convex_flutter/example/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/third_party/convex_flutter/example/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/third_party/convex_flutter/example/pubspec.yaml b/third_party/convex_flutter/example/pubspec.yaml new file mode 100644 index 00000000..0c1baaac --- /dev/null +++ b/third_party/convex_flutter/example/pubspec.yaml @@ -0,0 +1,99 @@ +name: convex_flutter_example +description: "Demonstrates how to use the convex_flutter plugin." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.8.1 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + convex_flutter: + # When depending on this package from a real application you should use: + # convex_flutter: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.8 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^5.0.0 + integration_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/third_party/convex_flutter/example/screenshots/app_screenshot.png b/third_party/convex_flutter/example/screenshots/app_screenshot.png new file mode 100644 index 00000000..684b201c Binary files /dev/null and b/third_party/convex_flutter/example/screenshots/app_screenshot.png differ diff --git a/third_party/convex_flutter/example/screenshots/messaging_screenshot.png b/third_party/convex_flutter/example/screenshots/messaging_screenshot.png new file mode 100644 index 00000000..45d93485 Binary files /dev/null and b/third_party/convex_flutter/example/screenshots/messaging_screenshot.png differ diff --git a/third_party/convex_flutter/example/test/widget_test.dart b/third_party/convex_flutter/example/test/widget_test.dart new file mode 100644 index 00000000..9266deaa --- /dev/null +++ b/third_party/convex_flutter/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:convex_flutter_example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} diff --git a/third_party/convex_flutter/example/web/favicon.png b/third_party/convex_flutter/example/web/favicon.png new file mode 100644 index 00000000..8aaa46ac Binary files /dev/null and b/third_party/convex_flutter/example/web/favicon.png differ diff --git a/third_party/convex_flutter/example/web/icons/Icon-192.png b/third_party/convex_flutter/example/web/icons/Icon-192.png new file mode 100644 index 00000000..b749bfef Binary files /dev/null and b/third_party/convex_flutter/example/web/icons/Icon-192.png differ diff --git a/third_party/convex_flutter/example/web/icons/Icon-512.png b/third_party/convex_flutter/example/web/icons/Icon-512.png new file mode 100644 index 00000000..88cfd48d Binary files /dev/null and b/third_party/convex_flutter/example/web/icons/Icon-512.png differ diff --git a/third_party/convex_flutter/example/web/icons/Icon-maskable-192.png b/third_party/convex_flutter/example/web/icons/Icon-maskable-192.png new file mode 100644 index 00000000..eb9b4d76 Binary files /dev/null and b/third_party/convex_flutter/example/web/icons/Icon-maskable-192.png differ diff --git a/third_party/convex_flutter/example/web/icons/Icon-maskable-512.png b/third_party/convex_flutter/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/third_party/convex_flutter/example/web/icons/Icon-maskable-512.png differ diff --git a/third_party/convex_flutter/example/web/index.html b/third_party/convex_flutter/example/web/index.html new file mode 100644 index 00000000..f01c73e0 --- /dev/null +++ b/third_party/convex_flutter/example/web/index.html @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + convex_flutter_example + + + + + + diff --git a/third_party/convex_flutter/example/web/manifest.json b/third_party/convex_flutter/example/web/manifest.json new file mode 100644 index 00000000..b6c15434 --- /dev/null +++ b/third_party/convex_flutter/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "convex_flutter_example", + "short_name": "convex_flutter_example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/third_party/convex_flutter/example/windows/CMakeLists.txt b/third_party/convex_flutter/example/windows/CMakeLists.txt new file mode 100644 index 00000000..110b0373 --- /dev/null +++ b/third_party/convex_flutter/example/windows/CMakeLists.txt @@ -0,0 +1,108 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(convex_flutter_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "convex_flutter_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(VERSION 3.14...3.25) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/windows/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/third_party/convex_flutter/example/windows/flutter/CMakeLists.txt b/third_party/convex_flutter/example/windows/flutter/CMakeLists.txt new file mode 100644 index 00000000..903f4899 --- /dev/null +++ b/third_party/convex_flutter/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,109 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# Set fallback configurations for older versions of the flutter tool. +if (NOT DEFINED FLUTTER_TARGET_PLATFORM) + set(FLUTTER_TARGET_PLATFORM "windows-x64") +endif() + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + ${FLUTTER_TARGET_PLATFORM} $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.cc b/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 00000000..8b6d4680 --- /dev/null +++ b/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void RegisterPlugins(flutter::PluginRegistry* registry) { +} diff --git a/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.h b/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 00000000..dc139d85 --- /dev/null +++ b/third_party/convex_flutter/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/third_party/convex_flutter/example/windows/flutter/generated_plugins.cmake b/third_party/convex_flutter/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 00000000..06a20a90 --- /dev/null +++ b/third_party/convex_flutter/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST + convex_flutter +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/third_party/convex_flutter/example/windows/runner/CMakeLists.txt b/third_party/convex_flutter/example/windows/runner/CMakeLists.txt new file mode 100644 index 00000000..394917c0 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/CMakeLists.txt @@ -0,0 +1,40 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add preprocessor definitions for the build version. +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") +target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/third_party/convex_flutter/example/windows/runner/Runner.rc b/third_party/convex_flutter/example/windows/runner/Runner.rc new file mode 100644 index 00000000..4bb12308 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) +#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD +#else +#define VERSION_AS_NUMBER 1,0,0,0 +#endif + +#if defined(FLUTTER_VERSION) +#define VERSION_AS_STRING FLUTTER_VERSION +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "convex_flutter_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "convex_flutter_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "convex_flutter_example.exe" "\0" + VALUE "ProductName", "convex_flutter_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/third_party/convex_flutter/example/windows/runner/flutter_window.cpp b/third_party/convex_flutter/example/windows/runner/flutter_window.cpp new file mode 100644 index 00000000..955ee303 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/flutter_window.cpp @@ -0,0 +1,71 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + + flutter_controller_->engine()->SetNextFrameCallback([&]() { + this->Show(); + }); + + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/third_party/convex_flutter/example/windows/runner/flutter_window.h b/third_party/convex_flutter/example/windows/runner/flutter_window.h new file mode 100644 index 00000000..6da0652f --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/third_party/convex_flutter/example/windows/runner/main.cpp b/third_party/convex_flutter/example/windows/runner/main.cpp new file mode 100644 index 00000000..8197cad9 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.Create(L"convex_flutter_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/third_party/convex_flutter/example/windows/runner/resource.h b/third_party/convex_flutter/example/windows/runner/resource.h new file mode 100644 index 00000000..66a65d1e --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/third_party/convex_flutter/example/windows/runner/resources/app_icon.ico b/third_party/convex_flutter/example/windows/runner/resources/app_icon.ico new file mode 100644 index 00000000..c04e20ca Binary files /dev/null and b/third_party/convex_flutter/example/windows/runner/resources/app_icon.ico differ diff --git a/third_party/convex_flutter/example/windows/runner/runner.exe.manifest b/third_party/convex_flutter/example/windows/runner/runner.exe.manifest new file mode 100644 index 00000000..153653e8 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/runner.exe.manifest @@ -0,0 +1,14 @@ + + + + + PerMonitorV2 + + + + + + + + + diff --git a/third_party/convex_flutter/example/windows/runner/utils.cpp b/third_party/convex_flutter/example/windows/runner/utils.cpp new file mode 100644 index 00000000..3a0b4651 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/utils.cpp @@ -0,0 +1,65 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + unsigned int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr) + -1; // remove the trailing null character + int input_length = (int)wcslen(utf16_string); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + input_length, utf8_string.data(), target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/third_party/convex_flutter/example/windows/runner/utils.h b/third_party/convex_flutter/example/windows/runner/utils.h new file mode 100644 index 00000000..3879d547 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/third_party/convex_flutter/example/windows/runner/win32_window.cpp b/third_party/convex_flutter/example/windows/runner/win32_window.cpp new file mode 100644 index 00000000..60608d0f --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/win32_window.cpp @@ -0,0 +1,288 @@ +#include "win32_window.h" + +#include +#include + +#include "resource.h" + +namespace { + +/// Window attribute that enables dark mode window decorations. +/// +/// Redefined in case the developer's machine has a Windows SDK older than +/// version 10.0.22000.0. +/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute +#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE +#define DWMWA_USE_IMMERSIVE_DARK_MODE 20 +#endif + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +/// Registry key for app theme preference. +/// +/// A value of 0 indicates apps should use dark mode. A non-zero or missing +/// value indicates apps should use light mode. +constexpr const wchar_t kGetPreferredBrightnessRegKey[] = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"; +constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + } + FreeLibrary(user32_module); +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registrar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::Create(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + UpdateTheme(window); + + return OnCreate(); +} + +bool Win32Window::Show() { + return ShowWindow(window_handle_, SW_SHOWNORMAL); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + + case WM_DWMCOLORIZATIONCOLORCHANGED: + UpdateTheme(hwnd); + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} + +void Win32Window::UpdateTheme(HWND const window) { + DWORD light_mode; + DWORD light_mode_size = sizeof(light_mode); + LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey, + kGetPreferredBrightnessRegValue, + RRF_RT_REG_DWORD, nullptr, &light_mode, + &light_mode_size); + + if (result == ERROR_SUCCESS) { + BOOL enable_dark_mode = light_mode == 0; + DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE, + &enable_dark_mode, sizeof(enable_dark_mode)); + } +} diff --git a/third_party/convex_flutter/example/windows/runner/win32_window.h b/third_party/convex_flutter/example/windows/runner/win32_window.h new file mode 100644 index 00000000..e901dde6 --- /dev/null +++ b/third_party/convex_flutter/example/windows/runner/win32_window.h @@ -0,0 +1,102 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates a win32 window with |title| that is positioned and sized using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size this function will scale the inputted width and height as + // as appropriate for the default monitor. The window is invisible until + // |Show| is called. Returns true if the window was created successfully. + bool Create(const std::wstring& title, const Point& origin, const Size& size); + + // Show the current window. Returns true if the window was successfully shown. + bool Show(); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + // Update the window frame's theme to match the system theme. + static void UpdateTheme(HWND const window); + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/third_party/convex_flutter/flutter_rust_bridge.yaml b/third_party/convex_flutter/flutter_rust_bridge.yaml new file mode 100644 index 00000000..9de311bb --- /dev/null +++ b/third_party/convex_flutter/flutter_rust_bridge.yaml @@ -0,0 +1,3 @@ +rust_input: crate +rust_root: rust/ +dart_output: lib/src/rust diff --git a/third_party/convex_flutter/ios/Classes/dummy_file.c b/third_party/convex_flutter/ios/Classes/dummy_file.c new file mode 100644 index 00000000..e06dab99 --- /dev/null +++ b/third_party/convex_flutter/ios/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/third_party/convex_flutter/ios/convex_flutter.podspec b/third_party/convex_flutter/ios/convex_flutter.podspec new file mode 100644 index 00000000..a40f968e --- /dev/null +++ b/third_party/convex_flutter/ios/convex_flutter.podspec @@ -0,0 +1,45 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint convex_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'convex_flutter' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'Flutter' + s.platform = :ios, '11.0' + + # Flutter.framework does not contain a i386 slice. + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust convex_flutter', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${BUILT_PRODUCTS_DIR}/libconvex_flutter.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libconvex_flutter.a', + } +end diff --git a/third_party/convex_flutter/lib/convex_flutter.dart b/third_party/convex_flutter/lib/convex_flutter.dart new file mode 100644 index 00000000..f88b0ad1 --- /dev/null +++ b/third_party/convex_flutter/lib/convex_flutter.dart @@ -0,0 +1,9 @@ +library; + +export 'src/rust/lib.dart'; +export 'src/rust/frb_generated.dart' show RustLib; +export 'src/convex_client.dart' + show ConvexClient, AuthHandleWrapper, TokenFetcher, AuthStateCallback; +export 'src/convex_config.dart' show ConvexConfig; +export 'src/connection_status.dart' show ConnectionStatus; +export 'src/app_lifecycle_event.dart' show AppLifecycleEvent; diff --git a/third_party/convex_flutter/lib/convex_flutter_web.dart b/third_party/convex_flutter/lib/convex_flutter_web.dart new file mode 100644 index 00000000..b5546e23 --- /dev/null +++ b/third_party/convex_flutter/lib/convex_flutter_web.dart @@ -0,0 +1,18 @@ +/// Web platform implementation of convex_flutter plugin. +/// +/// This file is automatically registered by Flutter when building for web. +library convex_flutter_web; + +import 'package:flutter_web_plugins/flutter_web_plugins.dart'; + +/// The web implementation of [ConvexFlutterPlatform]. +/// +/// This class is automatically registered when building for web. +/// The actual web functionality is provided by [WebConvexClient]. +class ConvexFlutterWeb { + /// Factory constructor for web platform plugin registration. + static void registerWith(Registrar registrar) { + // No platform channel needed for web - we use pure Dart WebSocket implementation + // The ConvexClient automatically selects WebConvexClient when kIsWeb is true + } +} diff --git a/third_party/convex_flutter/lib/src/app_lifecycle_event.dart b/third_party/convex_flutter/lib/src/app_lifecycle_event.dart new file mode 100644 index 00000000..e22b48df --- /dev/null +++ b/third_party/convex_flutter/lib/src/app_lifecycle_event.dart @@ -0,0 +1,26 @@ +/// Enum representing Flutter app lifecycle state changes. +/// +/// These events are emitted by the ConvexClient when the app +/// transitions between different lifecycle states. +/// +/// Example usage: +/// ```dart +/// ConvexClient.instance.lifecycleEvents.listen((event) { +/// if (event == AppLifecycleEvent.resumed) { +/// print('App came to foreground'); +/// } +/// }); +/// ``` +enum AppLifecycleEvent { + /// App has come to the foreground and is visible to the user + resumed, + + /// App is in the background but still running + paused, + + /// App is inactive (e.g., during a phone call or system dialog) + inactive, + + /// App is being terminated + detached, +} diff --git a/third_party/convex_flutter/lib/src/app_lifecycle_observer.dart b/third_party/convex_flutter/lib/src/app_lifecycle_observer.dart new file mode 100644 index 00000000..5dab7e64 --- /dev/null +++ b/third_party/convex_flutter/lib/src/app_lifecycle_observer.dart @@ -0,0 +1,45 @@ +import 'package:flutter/widgets.dart'; +import 'package:convex_flutter/src/app_lifecycle_event.dart'; + +/// Observes Flutter app lifecycle state changes and emits events. +/// +/// This class uses Flutter's WidgetsBindingObserver to monitor +/// when the app transitions between foreground, background, and +/// other lifecycle states. +/// +/// The observer automatically registers itself with WidgetsBinding +/// upon creation and should be disposed when no longer needed. +class AppLifecycleObserver with WidgetsBindingObserver { + /// Callback function invoked when app lifecycle state changes + final void Function(AppLifecycleEvent) onLifecycleChange; + + /// Creates a new lifecycle observer with the specified callback. + /// + /// The observer automatically registers itself with WidgetsBinding. + AppLifecycleObserver({required this.onLifecycleChange}) { + WidgetsBinding.instance.addObserver(this); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + final event = switch (state) { + AppLifecycleState.resumed => AppLifecycleEvent.resumed, + AppLifecycleState.paused => AppLifecycleEvent.paused, + AppLifecycleState.inactive => AppLifecycleEvent.inactive, + AppLifecycleState.detached => AppLifecycleEvent.detached, + _ => null, + }; + + if (event != null) { + onLifecycleChange(event); + } + } + + /// Disposes the observer and unregisters it from WidgetsBinding. + /// + /// Call this method when the observer is no longer needed to prevent + /// memory leaks. + void dispose() { + WidgetsBinding.instance.removeObserver(this); + } +} diff --git a/third_party/convex_flutter/lib/src/connection_status.dart b/third_party/convex_flutter/lib/src/connection_status.dart new file mode 100644 index 00000000..5ab60500 --- /dev/null +++ b/third_party/convex_flutter/lib/src/connection_status.dart @@ -0,0 +1,15 @@ +/// Enum representing the possible connection states when checking +/// connectivity to the Convex backend. +enum ConnectionStatus { + /// Connection check has not been performed yet + unknown, + + /// Successfully connected to the backend + connected, + + /// Connection check timed out + timeout, + + /// An error occurred during the connection check + error, +} diff --git a/third_party/convex_flutter/lib/src/convex_client.dart b/third_party/convex_flutter/lib/src/convex_client.dart new file mode 100644 index 00000000..b98ff3af --- /dev/null +++ b/third_party/convex_flutter/lib/src/convex_client.dart @@ -0,0 +1,421 @@ +import 'dart:async'; + +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; +import 'package:convex_flutter/src/impl/convex_client_factory.dart'; +import 'package:convex_flutter/src/rust/lib.dart' + show WebSocketConnectionState, SubscriptionHandle, AuthHandle; +import 'package:convex_flutter/src/connection_status.dart'; +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/app_lifecycle_event.dart'; + +/// Callback type for fetching authentication tokens. +/// Should return a JWT token string, or null to sign out. +typedef TokenFetcher = Future Function(); + +/// Callback type for authentication state changes. +typedef AuthStateCallback = void Function(bool isAuthenticated); + +/// A client for interacting with a Convex backend service. +/// +/// The ConvexClient provides methods for executing queries, mutations, actions and +/// managing real-time subscriptions with a Convex backend. +/// +/// This client automatically selects the appropriate implementation based on platform: +/// - **Mobile/Desktop** (Android, iOS, macOS, Windows, Linux): Uses FFI + Rust SDK +/// - **Web**: Uses pure Dart WebSocket implementation (no Rust required) +/// +/// Example usage: +/// +/// ```dart +/// // Initialize the client +/// await ConvexClient.initialize( +/// ConvexConfig( +/// deploymentUrl: "https://my-app.convex.cloud", +/// clientId: "flutter-app-1.0", +/// ), +/// ); +/// +/// // Execute a query +/// final result = await ConvexClient.instance.query( +/// "messages:list", +/// {"limit": "10"} +/// ); +/// +/// // Subscribe to real-time updates +/// final subscription = await ConvexClient.instance.subscribe( +/// name: "messages:list", +/// args: {}, +/// onUpdate: (value) { +/// print("New messages: $value"); +/// }, +/// onError: (message, value) { +/// print("Error: $message"); +/// } +/// ); +/// +/// // Execute a mutation +/// await ConvexClient.instance.mutation( +/// name: "messages:send", +/// args: { +/// "body": "Hello!", +/// "author": "User123" +/// } +/// ); +/// +/// // Cancel subscription when done +/// subscription.cancel(); +/// ``` +class ConvexClient { + /// Private static instance for singleton pattern + static ConvexClient? _instance; + + /// The platform-specific implementation (Native or Web) + final IConvexClient _impl; + + /// Private constructor + ConvexClient._(this._impl); + + /// Public getter to access singleton instance + /// Throws StateError if accessed before initialization + static ConvexClient get instance { + if (_instance == null) { + throw StateError( + 'ConvexClient not initialized. ' + 'Call ConvexClient.initialize() first.', + ); + } + return _instance!; + } + + /// Initializes the ConvexClient singleton instance with configuration. + /// + /// This method must be called once before accessing [instance]. + /// Subsequent calls will throw a StateError. + /// + /// The client automatically selects the appropriate platform implementation: + /// - **Web**: Pure Dart WebSocket (no Rust required) + /// - **Mobile/Desktop**: FFI + Rust SDK (requires Rust toolchain for building) + /// + /// Example usage: + /// ```dart + /// await ConvexClient.initialize( + /// ConvexConfig( + /// deploymentUrl: "https://your-app.convex.cloud", + /// clientId: "flutter-app", + /// operationTimeout: Duration(seconds: 30), + /// ), + /// ); + /// ``` + static Future initialize(ConvexConfig config) async { + if (_instance != null) { + throw StateError('ConvexClient already initialized'); + } + + // Create platform-specific implementation using factory + // Factory automatically selects: + // - WebConvexClient (pure Dart) on web + // - NativeConvexClient (FFI + Rust SDK) on native platforms + final IConvexClient impl = await createPlatformClient(config); + + // Create singleton with chosen implementation + _instance = ConvexClient._(impl); + } + + /// Initializes the ConvexClient singleton instance (DEPRECATED). + /// + /// This method is deprecated. Use [initialize] with [ConvexConfig] instead. + /// + /// Example migration: + /// ```dart + /// // Old way (deprecated) + /// await ConvexClient.init(deploymentUrl: "...", clientId: "..."); + /// + /// // New way + /// await ConvexClient.initialize( + /// ConvexConfig(deploymentUrl: "...", clientId: "..."), + /// ); + /// ``` + @Deprecated('Use initialize(ConvexConfig) instead') + static Future init({ + required String deploymentUrl, + required String clientId, + }) async { + if (_instance == null) { + await initialize( + ConvexConfig(deploymentUrl: deploymentUrl, clientId: clientId), + ); + } + return _instance!; + } + + // ============================================================================ + // Public API - All methods delegate to platform-specific implementation + // ============================================================================ + + /// Configuration for this client instance + ConvexConfig get config => _impl.config; + + /// Executes a Convex query operation with timeout. + /// + /// [name] - Name of the query function to execute (e.g., "messages:list") + /// [args] - Map of arguments to pass to the query + /// + /// Returns the query result as a JSON string. + /// Throws [TimeoutException] if the operation exceeds [config.operationTimeout]. + Future query(String name, Map args) => + _impl.query(name, args); + + /// Executes a Convex mutation operation with timeout. + /// + /// [name] - Name of the mutation function to execute + /// [args] - Map of arguments to pass to the mutation + /// + /// Returns the mutation result as a JSON string. + /// Throws [TimeoutException] if the operation exceeds [config.operationTimeout]. + Future mutation({ + required String name, + required Map args, + }) => _impl.mutation(name: name, args: args); + + /// Executes a Convex action operation with timeout. + /// + /// [name] - Name of the action function to execute + /// [args] - Map of arguments to pass to the action + /// + /// Returns the action result as a JSON string. + /// Throws [TimeoutException] if the operation exceeds [config.operationTimeout]. + Future action({ + required String name, + required Map args, + }) => _impl.action(name: name, args: args); + + /// Creates a real-time subscription to a Convex query. + /// + /// [name] - Name of the query function to subscribe to + /// [args] - Map of arguments for the subscription + /// [onUpdate] - Callback function called when new data arrives + /// [onError] - Callback function called when an error occurs + /// + /// Returns a handle that can be used to cancel the subscription. + Future subscribe({ + required String name, + required Map args, + required void Function(String) onUpdate, + required void Function(String, String?) onError, + }) => _impl.subscribe( + name: name, + args: args, + onUpdate: onUpdate, + onError: onError, + ); + + // ============================================================================ + // Authentication API + // ============================================================================ + + /// Sets the authentication token for the client (simple/static). + /// + /// Use this for simple auth scenarios where you manage token refresh externally. + /// For automatic token refresh, use [setAuthWithRefresh] instead. + /// + /// [token] - The authentication token to set, or null to clear auth. + /// + /// Example usage: + /// ```dart + /// // Set auth with a token + /// await client.setAuth(token: 'eyJhbGciOiJSUzI1NiIs...'); + /// + /// // Clear auth + /// await client.setAuth(token: null); + /// ``` + Future setAuth({required String? token}) => _impl.setAuth(token: token); + + /// Sets up authentication with automatic token refresh. + /// + /// This is the recommended way to handle authentication. The [fetchToken] + /// callback will be called: + /// - Immediately to get the initial token + /// - Automatically when the token is about to expire (60 seconds before) + /// + /// Example usage: + /// ```dart + /// final authHandle = await client.setAuthWithRefresh( + /// fetchToken: () async { + /// // Get token from your auth provider (Clerk, Auth0, Firebase, etc.) + /// return await FirebaseAuth.instance.currentUser?.getIdToken(); + /// }, + /// onAuthChange: (isAuthenticated) { + /// print('Auth state changed: $isAuthenticated'); + /// }, + /// ); + /// + /// // Later, when signing out: + /// authHandle.dispose(); + /// ``` + /// + /// [fetchToken] - Async function that returns a JWT token, or null to sign out. + /// [onAuthChange] - Optional callback invoked when auth state changes. + /// + /// Returns an [AuthHandleWrapper] that can be used to dispose the auth session. + Future setAuthWithRefresh({ + required TokenFetcher fetchToken, + AuthStateCallback? onAuthChange, + }) async { + final handle = await _impl.setAuthWithRefresh( + tokenFetcher: fetchToken, + onAuthChange: onAuthChange, + ); + return AuthHandleWrapper._(handle); + } + + /// Clears authentication and disposes any active auth refresh loop. + /// + /// This will: + /// - Stop any running token refresh loop + /// - Clear the auth token from the Convex client + /// - Emit `false` on the [authState] stream + Future clearAuth() => _impl.clearAuth(); + + /// Stream of authentication state changes. + /// Emits `true` when authenticated, `false` when not. + /// + /// Example usage: + /// ```dart + /// ConvexClient.instance.authState.listen((isAuthenticated) { + /// setState(() => _isLoggedIn = isAuthenticated); + /// }); + /// ``` + Stream get authState => _impl.authState; + + /// Current authentication state (synchronous). + /// Returns `true` if authenticated via [setAuthWithRefresh], `false` otherwise. + bool get isAuthenticated => _impl.isAuthenticated; + + // ============================================================================ + // Connection Management API + // ============================================================================ + + /// Stream of WebSocket connection state changes. + /// + /// Emits state whenever the underlying WebSocket connection changes + /// between Connected and Connecting states. This provides real-time + /// connection monitoring without manual polling. + /// + /// Example usage: + /// ```dart + /// ConvexClient.instance.connectionState.listen((state) { + /// if (state == WebSocketConnectionState.connected) { + /// print('Connected to Convex!'); + /// } + /// }); + /// ``` + Stream get connectionState => _impl.connectionState; + + /// Current WebSocket connection state (synchronous). + /// Returns the most recent state from the WebSocket connection. + WebSocketConnectionState get currentConnectionState => + _impl.currentConnectionState; + + /// Convenience getter - returns true if WebSocket is currently connected. + bool get isConnected => _impl.isConnected; + + /// Manually checks the connection status to the Convex backend. + /// + /// **DEPRECATED:** Use the [connectionState] stream for real-time state tracking. + /// This method is slower and less accurate than the WebSocket state stream. + /// + /// This method uses the [ConvexConfig.healthCheckQuery] to verify connectivity. + /// If no health check query is configured, throws a [StateError]. + /// + /// Returns [ConnectionStatus.connected] if the connection is working, + /// [ConnectionStatus.timeout] if the check times out, or + /// [ConnectionStatus.error] if an error occurs. + /// + /// Example usage (deprecated): + /// ```dart + /// final status = await ConvexClient.instance.checkConnection(); + /// if (status == ConnectionStatus.connected) { + /// print('Connected!'); + /// } + /// ``` + /// + /// Recommended alternative - use the real-time connection state stream: + /// ```dart + /// ConvexClient.instance.connectionState.listen((state) { + /// if (state == WebSocketConnectionState.connected) { + /// print('Connected!'); + /// } + /// }); + /// ``` + @Deprecated('Use connectionState stream for real-time connection monitoring') + Future checkConnection() => _impl.checkConnection(); + + /// Attempts to reconnect to the Convex backend. + /// + /// This method restarts the native WebSocket and returns true after the + /// connection-state stream observes the complete reconnect transition. + /// + /// Typically called after the app resumes from background or + /// after detecting a network interruption. + /// + /// Example usage: + /// ```dart + /// ConvexClient.instance.lifecycleEvents.listen((event) { + /// if (event == AppLifecycleEvent.resumed) { + /// final connected = await ConvexClient.instance.reconnect(); + /// if (connected) { + /// print('Reconnected successfully'); + /// } + /// } + /// }); + /// ``` + Future reconnect() => _impl.reconnect(); + + // ============================================================================ + // Lifecycle Management API + // ============================================================================ + + /// Stream of app lifecycle events (foreground/background transitions). + /// + /// Emits events when the app transitions between foreground/background states. + /// Useful for handling reconnection or other lifecycle-based logic. + /// + /// Example usage: + /// ```dart + /// ConvexClient.instance.lifecycleEvents.listen((event) { + /// if (event == AppLifecycleEvent.resumed) { + /// // App came to foreground + /// ConvexClient.instance.reconnect(); + /// } + /// }); + /// ``` + Stream get lifecycleEvents => _impl.lifecycleEvents; + + // ============================================================================ + // Resource Management + // ============================================================================ + + /// Dispose the client and clean up resources. + /// + /// Call this when you're done using the client to free up resources. + /// Note: This is typically not needed as the client is a singleton, + /// but can be useful in testing scenarios. + void dispose() => _impl.dispose(); +} + +/// Wrapper for auth handle providing Dart-friendly API. +/// +/// Returned by [ConvexClient.setAuthWithRefresh] to control the auth session. +class AuthHandleWrapper { + final AuthHandle _handle; + + AuthHandleWrapper._(this._handle); + + /// Whether the user is currently authenticated. + bool get isAuthenticated => _handle.isAuthenticated(); + + /// Dispose the auth session, stopping token refresh and clearing auth. + /// + /// Call this when signing out or when you no longer need automatic token refresh. + void dispose() => _handle.dispose(); +} diff --git a/third_party/convex_flutter/lib/src/convex_config.dart b/third_party/convex_flutter/lib/src/convex_config.dart new file mode 100644 index 00000000..d48f3751 --- /dev/null +++ b/third_party/convex_flutter/lib/src/convex_config.dart @@ -0,0 +1,53 @@ +/// Configuration for ConvexClient initialization. +/// +/// This class holds all configuration options for initializing +/// the Convex client singleton. +/// +/// Example usage: +/// ```dart +/// await ConvexClient.initialize( +/// ConvexConfig( +/// deploymentUrl: "https://your-app.convex.cloud", +/// clientId: "flutter-app", +/// operationTimeout: Duration(seconds: 30), +/// healthCheckQuery: "system:ping", +/// ), +/// ); +/// ``` +class ConvexConfig { + /// The URL of your Convex deployment. + /// + /// Example: "https://my-app.convex.cloud" + final String deploymentUrl; + + /// Optional unique identifier for this client instance. + /// + /// If not provided, defaults to 'flutter-client'. + final String? clientId; + + /// Timeout duration for all query, mutation, and action operations. + /// + /// Operations that take longer than this duration will throw + /// a TimeoutException. Defaults to 30 seconds. + final Duration operationTimeout; + + /// Optional query name to use for manual connection health checks. + /// + /// This should be the name of a lightweight query in your Convex backend + /// that can be used to verify the connection is working. + /// + /// Example: "system:ping" or any query that returns quickly. + /// + /// If null, calling `ConvexClient.instance.checkConnection()` will throw + /// a StateError. You can still check connection by attempting regular + /// queries and catching TimeoutException. + final String? healthCheckQuery; + + /// Creates a new ConvexConfig with the specified options. + const ConvexConfig({ + required this.deploymentUrl, + this.clientId, + this.operationTimeout = const Duration(seconds: 30), + this.healthCheckQuery, + }); +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_factory.dart b/third_party/convex_flutter/lib/src/impl/convex_client_factory.dart new file mode 100644 index 00000000..04e7ea12 --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_factory.dart @@ -0,0 +1,23 @@ +/// Factory for creating platform-specific ConvexClient implementations. +/// +/// Uses conditional imports to avoid compiling web-only code on native platforms. +library convex_client_factory; + +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; + +// Import appropriate implementation based on platform +import 'convex_client_factory_io.dart' + if (dart.library.js_interop) 'convex_client_factory_web.dart'; + +/// Creates the appropriate platform-specific ConvexClient implementation. +/// +/// This factory method uses conditional imports to: +/// - Return NativeConvexClient on native platforms (iOS, Android, macOS, Windows, Linux) +/// - Return WebConvexClient on web platform +/// +/// This prevents web-only libraries (dart:js_interop, package:web) from being +/// compiled into native builds, which would cause compilation errors. +Future createPlatformClient(ConvexConfig config) async { + return await createClientImpl(config); +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_factory_io.dart b/third_party/convex_flutter/lib/src/impl/convex_client_factory_io.dart new file mode 100644 index 00000000..57252d56 --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_factory_io.dart @@ -0,0 +1,12 @@ +/// Native platform (IO) implementation factory. +/// +/// This file is imported on iOS, Android, macOS, Windows, and Linux platforms. + +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; +import 'package:convex_flutter/src/impl/convex_client_native.dart'; + +/// Creates a NativeConvexClient for native platforms. +Future createClientImpl(ConvexConfig config) async { + return await NativeConvexClient.create(config); +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_factory_web.dart b/third_party/convex_flutter/lib/src/impl/convex_client_factory_web.dart new file mode 100644 index 00000000..58d51411 --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_factory_web.dart @@ -0,0 +1,12 @@ +/// Web platform implementation factory. +/// +/// This file is imported only on web platform (when dart:js_interop is available). + +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; +import 'package:convex_flutter/src/impl/convex_client_web.dart'; + +/// Creates a WebConvexClient for web platform. +Future createClientImpl(ConvexConfig config) async { + return await WebConvexClient.create(config); +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_interface.dart b/third_party/convex_flutter/lib/src/impl/convex_client_interface.dart new file mode 100644 index 00000000..1b1bed41 --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_interface.dart @@ -0,0 +1,162 @@ +import 'dart:async'; + +import 'package:convex_flutter/src/rust/lib.dart' show WebSocketConnectionState, SubscriptionHandle, AuthHandle; +import 'package:convex_flutter/src/connection_status.dart'; +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/app_lifecycle_event.dart'; + +/// Abstract interface for platform-specific Convex client implementations. +/// +/// This interface defines the contract that both native (FFI) and web (pure Dart) +/// implementations must follow, ensuring API consistency across all platforms. +/// +/// Implementations: +/// - [NativeConvexClient]: Uses Flutter Rust Bridge (FFI) to call Convex Rust SDK +/// - [WebConvexClient]: Uses pure Dart WebSocket for web platform +abstract class IConvexClient { + /// Configuration for this client instance + ConvexConfig get config; + + // ============================================================================ + // Core Operations + // ============================================================================ + + /// Executes a Convex query operation. + /// + /// [name] - Name of the query function to execute (e.g., "messages:list") + /// [args] - Map of arguments to pass to the query + /// + /// Returns the query result as a JSON string. + /// + /// Throws: + /// - [TimeoutException] if operation exceeds configured timeout + /// - [ClientError] for Convex-specific errors + Future query(String name, Map args); + + /// Executes a Convex mutation operation. + /// + /// [name] - Name of the mutation function to execute + /// [args] - Map of arguments to pass to the mutation + /// + /// Returns the mutation result as a JSON string. + /// + /// Throws: + /// - [TimeoutException] if operation exceeds configured timeout + /// - [ClientError] for Convex-specific errors + Future mutation({ + required String name, + required Map args, + }); + + /// Executes a Convex action operation. + /// + /// [name] - Name of the action function to execute + /// [args] - Map of arguments to pass to the action + /// + /// Returns the action result as a JSON string. + /// + /// Throws: + /// - [TimeoutException] if operation exceeds configured timeout + /// - [ClientError] for Convex-specific errors + Future action({ + required String name, + required Map args, + }); + + /// Creates a real-time subscription to a Convex query. + /// + /// [name] - Name of the query function to subscribe to + /// [args] - Map of arguments for the subscription + /// [onUpdate] - Callback function called when new data arrives + /// [onError] - Callback function called when an error occurs + /// + /// Returns a handle that can be used to cancel the subscription. + Future subscribe({ + required String name, + required Map args, + required void Function(String) onUpdate, + required void Function(String, String?) onError, + }); + + // ============================================================================ + // Authentication + // ============================================================================ + + /// Sets the authentication token for the client. + /// + /// [token] - The JWT authentication token to set, or null to clear + /// + /// Used to authenticate requests to the Convex backend. + Future setAuth({required String? token}); + + /// Sets authentication with automatic token refresh. + /// + /// [tokenFetcher] - Function that returns a fresh JWT token when called + /// [onAuthChange] - Optional callback for auth state changes + /// + /// Returns an [AuthHandle] that manages the auth session and token refresh. + Future setAuthWithRefresh({ + required Future Function() tokenFetcher, + void Function(bool isAuthenticated)? onAuthChange, + }); + + /// Clears the authentication token and stops any active token refresh. + Future clearAuth(); + + /// Stream of authentication state changes. + /// + /// Emits `true` when authenticated, `false` when not authenticated. + Stream get authState; + + /// Returns whether the user is currently authenticated. + bool get isAuthenticated; + + // ============================================================================ + // Connection Management + // ============================================================================ + + /// Stream of WebSocket connection state changes. + /// + /// Emits [WebSocketConnectionState.connected] when connection is established, + /// [WebSocketConnectionState.connecting] when connecting or reconnecting. + /// + /// This is the recommended way to monitor connection status. + Stream get connectionState; + + /// Returns the current WebSocket connection state (synchronous). + WebSocketConnectionState get currentConnectionState; + + /// Returns whether the WebSocket is currently connected. + bool get isConnected; + + /// Manually checks connection status using a health check query. + /// + /// **Deprecated**: Use [connectionState] stream instead for real-time monitoring. + /// + /// Returns [ConnectionStatus] indicating connection state. + @Deprecated('Use connectionState stream instead') + Future checkConnection(); + + /// Manually triggers a reconnection attempt. + /// + /// Returns `true` if reconnection was successful, `false` otherwise. + Future reconnect(); + + // ============================================================================ + // Lifecycle Management + // ============================================================================ + + /// Stream of app lifecycle events (foreground/background transitions). + /// + /// Useful for managing connections when app state changes. + Stream get lifecycleEvents; + + // ============================================================================ + // Resource Management + // ============================================================================ + + /// Disposes of client resources and closes connections. + /// + /// Should be called when the client is no longer needed. + void dispose(); +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_native.dart b/third_party/convex_flutter/lib/src/impl/convex_client_native.dart new file mode 100644 index 00000000..5ac70dbc --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_native.dart @@ -0,0 +1,307 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; +import 'package:convex_flutter/src/rust/lib.dart'; +import 'package:convex_flutter/src/rust/frb_generated.dart'; +import 'package:convex_flutter/src/utils.dart'; +import 'package:convex_flutter/src/connection_status.dart'; +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/app_lifecycle_event.dart'; +import 'package:convex_flutter/src/app_lifecycle_observer.dart'; + +/// Native (FFI-based) implementation of Convex client. +/// +/// This implementation uses Flutter Rust Bridge to call into the official +/// Convex Rust SDK for mobile and desktop platforms (Android, iOS, macOS, +/// Windows, Linux). +/// +/// For web platform, use [WebConvexClient] instead. +class NativeConvexClient implements IConvexClient { + /// The underlying Rust FFI client + final MobileConvexClient _rustClient; + + /// Configuration for this client + @override + final ConvexConfig config; + + /// Stream controller for auth state changes + final StreamController _authStateController = + StreamController.broadcast(); + + /// Stream controller for lifecycle events + final StreamController _lifecycleController = + StreamController.broadcast(); + + /// Stream controller for WebSocket connection state changes + final StreamController _connectionStateController = + StreamController.broadcast(); + + /// Current connection state (cached for sync access) + WebSocketConnectionState _currentConnectionState = + WebSocketConnectionState.connecting; + + /// Current auth handle (if using refresh-based auth) + AuthHandle? _currentAuthHandle; + + /// Lifecycle observer for app state changes + late final AppLifecycleObserver _lifecycleObserver; + + /// Private constructor + NativeConvexClient._(this._rustClient, this.config); + + /// Factory method to create and initialize a native client. + /// + /// This handles: + /// - Rust FFI library initialization + /// - WebSocket state listener setup + /// - Lifecycle observer setup + static Future create(ConvexConfig config) async { + // Initialize Rust FFI library + await RustLib.init(); + + // Create Rust client instance + final rustClient = MobileConvexClient( + deploymentUrl: config.deploymentUrl, + clientId: config.clientId ?? 'flutter-client', + ); + + // Create native client wrapper + final client = NativeConvexClient._(rustClient, config); + + // Setup connection state listener BEFORE any operations + // This prevents race conditions where state changes are missed + await client._setupConnectionStateListener(); + + // Setup lifecycle observer + client._lifecycleObserver = AppLifecycleObserver( + onLifecycleChange: (event) { + if (!client._lifecycleController.isClosed) { + client._lifecycleController.add(event); + } + }, + ); + + return client; + } + + /// Sets up the WebSocket connection state listener. + /// + /// This must be called before any queries/mutations to capture all state changes. + Future _setupConnectionStateListener() async { + debugPrint( + '=== [NativeConvexClient] Setting up WebSocket state listener ===', + ); + debugPrint( + '=== [NativeConvexClient] Current state: ${_currentConnectionState.name} ===', + ); + + try { + await _rustClient.onWebsocketStateChange( + onStateChange: (state) async { + debugPrint( + '=== [NativeConvexClient] State changed: ${state.name} ===', + ); + _currentConnectionState = state; + if (!_connectionStateController.isClosed) { + _connectionStateController.add(state); + } + debugPrint('=== [NativeConvexClient] Stream emission complete ==='); + }, + ); + debugPrint( + '=== [NativeConvexClient] Listener registered successfully ===', + ); + } catch (e) { + debugPrint('ERROR: [NativeConvexClient] Listener setup failed: $e'); + rethrow; + } + } + + // ============================================================================ + // IConvexClient Implementation - Core Operations + // ============================================================================ + + @override + Future query(String name, Map args) async { + final formattedArgs = buildArgs(args); + return await _rustClient + .query(name: name, args: formattedArgs) + .timeout(config.operationTimeout); + } + + @override + Future mutation({ + required String name, + required Map args, + }) async { + final formattedArgs = buildArgs(args); + return await _rustClient + .mutation(name: name, args: formattedArgs) + .timeout(config.operationTimeout); + } + + @override + Future action({ + required String name, + required Map args, + }) async { + final formattedArgs = buildArgs(args); + return await _rustClient + .action(name: name, args: formattedArgs) + .timeout(config.operationTimeout); + } + + @override + Future subscribe({ + required String name, + required Map args, + required void Function(String) onUpdate, + required void Function(String, String?) onError, + }) async { + final formattedArgs = buildArgs(args); + return await _rustClient.subscribe( + name: name, + args: formattedArgs, + onUpdate: (value) => onUpdate(value), + onError: (message, value) => onError(message, value), + ); + } + + // ============================================================================ + // IConvexClient Implementation - Authentication + // ============================================================================ + + @override + Future setAuth({required String? token}) async { + // Clear any existing refresh-based auth + _currentAuthHandle?.dispose(); + _currentAuthHandle = null; + + await _rustClient.setAuth(token: token); + if (!_authStateController.isClosed) { + _authStateController.add(token != null); + } + } + + @override + Future setAuthWithRefresh({ + required Future Function() tokenFetcher, + void Function(bool isAuthenticated)? onAuthChange, + }) async { + // Dispose any existing auth handle + _currentAuthHandle?.dispose(); + + final handle = await _rustClient.setAuthWithRefresh( + fetchToken: () async => await tokenFetcher(), + onAuthChange: (bool isAuth) async { + onAuthChange?.call(isAuth); + if (!_authStateController.isClosed) { + _authStateController.add(isAuth); + } + }, + ); + + _currentAuthHandle = handle; + return handle; + } + + @override + Future clearAuth() async { + _currentAuthHandle?.dispose(); + _currentAuthHandle = null; + await _rustClient.setAuth(token: null); + if (!_authStateController.isClosed) { + _authStateController.add(false); + } + } + + @override + Stream get authState => _authStateController.stream; + + @override + bool get isAuthenticated => _currentAuthHandle?.isAuthenticated() ?? false; + + // ============================================================================ + // IConvexClient Implementation - Connection Management + // ============================================================================ + + @override + Stream get connectionState => + _connectionStateController.stream; + + @override + WebSocketConnectionState get currentConnectionState => + _currentConnectionState; + + @override + bool get isConnected => + _currentConnectionState == WebSocketConnectionState.connected; + + @override + @Deprecated('Use connectionState stream for real-time monitoring') + Future checkConnection() async { + if (config.healthCheckQuery == null) { + throw StateError( + 'No health check query configured. ' + 'Set healthCheckQuery in ConvexConfig or use a real query.', + ); + } + + try { + await _rustClient + .query(name: config.healthCheckQuery!, args: {}) + .timeout(config.operationTimeout); + return ConnectionStatus.connected; + } on TimeoutException { + return ConnectionStatus.timeout; + } catch (e) { + return ConnectionStatus.error; + } + } + + @override + Future reconnect() async { + final states = StreamIterator(connectionState); + var sawConnecting = false; + final deadline = DateTime.now().add(config.operationTimeout); + try { + await _rustClient.reconnectNow(reason: 'convex_flutter:manual'); + while (DateTime.now().isBefore(deadline)) { + final remaining = deadline.difference(DateTime.now()); + if (!await states.moveNext().timeout(remaining)) return false; + if (states.current == WebSocketConnectionState.connecting) { + sawConnecting = true; + } else if (sawConnecting && + states.current == WebSocketConnectionState.connected) { + return true; + } + } + return false; + } on TimeoutException { + return false; + } finally { + await states.cancel(); + } + } + + // ============================================================================ + // IConvexClient Implementation - Lifecycle Management + // ============================================================================ + + @override + Stream get lifecycleEvents => _lifecycleController.stream; + + // ============================================================================ + // IConvexClient Implementation - Resource Management + // ============================================================================ + + @override + void dispose() { + _currentAuthHandle?.dispose(); + _lifecycleObserver.dispose(); + _authStateController.close(); + _lifecycleController.close(); + _connectionStateController.close(); + } +} diff --git a/third_party/convex_flutter/lib/src/impl/convex_client_web.dart b/third_party/convex_flutter/lib/src/impl/convex_client_web.dart new file mode 100644 index 00000000..870eee2d --- /dev/null +++ b/third_party/convex_flutter/lib/src/impl/convex_client_web.dart @@ -0,0 +1,875 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:js_interop'; +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:web/web.dart' as web; +import 'package:convex_flutter/src/impl/convex_client_interface.dart'; +import 'package:convex_flutter/src/rust/lib.dart' show WebSocketConnectionState, SubscriptionHandle, AuthHandle; +import 'package:convex_flutter/src/connection_status.dart'; +import 'package:convex_flutter/src/convex_config.dart'; +import 'package:convex_flutter/src/app_lifecycle_event.dart'; +import 'package:convex_flutter/src/app_lifecycle_observer.dart'; + +/// Web (pure Dart) implementation of Convex client. +/// +/// This implementation uses the browser's native WebSocket API for web platform, +/// avoiding the need for Rust toolchain or FFI. It implements the same +/// [IConvexClient] interface as [NativeConvexClient], ensuring API compatibility +/// across all platforms. +/// +/// For mobile/desktop platforms, use [NativeConvexClient] instead. +class WebConvexClient implements IConvexClient { + /// Configuration for this client + @override + final ConvexConfig config; + + /// WebSocket connection to Convex backend + web.WebSocket? _ws; + + /// Stream controller for auth state changes + final StreamController _authStateController = + StreamController.broadcast(); + + /// Stream controller for lifecycle events + final StreamController _lifecycleController = + StreamController.broadcast(); + + /// Stream controller for WebSocket connection state changes + final StreamController _connectionStateController = + StreamController.broadcast(); + + /// Current connection state (cached for sync access) + WebSocketConnectionState _currentConnectionState = + WebSocketConnectionState.connecting; + + /// Current auth token + String? _currentAuthToken; + + /// Lifecycle observer for app state changes + late final AppLifecycleObserver _lifecycleObserver; + + /// Message ID counter for generating unique request IDs + int _messageIdCounter = 0; + + /// Session ID for Convex sync protocol + String? _sessionId; + + /// Query ID counter for subscriptions + int _queryIdCounter = 0; + + /// Query set version counter for ModifyQuerySet messages + int _querySetVersion = 0; + + /// Pending requests waiting for responses (query, mutation, action) + final Map> _pendingRequests = {}; + + /// Active subscriptions + final Map _subscriptions = {}; + + /// Reconnection attempt counter + int _reconnectAttempts = 0; + + /// Maximum reconnection attempts + static const int _maxReconnectAttempts = 10; + + /// Base reconnection delay + static const Duration _baseReconnectDelay = Duration(seconds: 1); + + /// Timer for reconnection + Timer? _reconnectTimer; + + /// Whether client is disposed + bool _isDisposed = false; + + /// Private constructor + WebConvexClient._(this.config); + + /// Factory method to create and initialize a web client. + /// + /// This handles: + /// - WebSocket connection setup + /// - Event listener registration + /// - Lifecycle observer setup + static Future create(ConvexConfig config) async { + debugPrint('=== [WebConvexClient] Creating web client ==='); + + final client = WebConvexClient._(config); + + // Setup lifecycle observer + // Note: On web, we don't reconnect on lifecycle events because: + // 1. Page navigation triggers lifecycle events but doesn't disconnect WebSocket + // 2. WebSocket onclose handler already manages reconnection + // 3. Browser tab visibility changes are the only real "background" events + client._lifecycleObserver = AppLifecycleObserver( + onLifecycleChange: (event) { + client._lifecycleController.add(event); + // Do NOT trigger reconnection on web - let WebSocket manage itself + debugPrint('=== [WebConvexClient] Lifecycle event: ${event.name} (no action on web) ==='); + }, + ); + + // Establish WebSocket connection + await client._connect(); + + debugPrint('=== [WebConvexClient] Client created successfully ==='); + return client; + } + + /// Establishes WebSocket connection to Convex backend. + Future _connect() async { + if (_isDisposed) return; + + debugPrint('=== [WebConvexClient] Connecting to Convex ==='); + + try { + // Convert HTTPS to WSS URL with correct Convex sync endpoint + // Format: wss://deployment.convex.cloud/api/{version}/sync + final wsUrl = config.deploymentUrl.replaceFirst('https', 'wss'); + final fullUrl = '$wsUrl/api/sync'; + + debugPrint('=== [WebConvexClient] WebSocket URL: $fullUrl ==='); + + // Update state to connecting + _updateConnectionState(WebSocketConnectionState.connecting); + + // Create WebSocket connection + _ws = web.WebSocket(fullUrl); + + // Setup event listeners + _setupWebSocketListeners(); + + debugPrint('=== [WebConvexClient] WebSocket connection initiated ==='); + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Connection failed: $e'); + _scheduleReconnect(); + } + } + + /// Sets up WebSocket event listeners. + void _setupWebSocketListeners() { + final ws = _ws; + if (ws == null) return; + + // Connection opened + ws.onopen = (web.Event event) { + debugPrint('=== [WebConvexClient] WebSocket opened ==='); + _reconnectAttempts = 0; // Reset reconnection counter + _querySetVersion = 0; // Reset query set version for new connection + _updateConnectionState(WebSocketConnectionState.connected); + + // Send Connect handshake (required by Convex protocol) + _sendConnectMessage(); + + // Send auth token if available + if (_currentAuthToken != null) { + _sendAuthMessage(_currentAuthToken!); + } + }.toJS; + + // Connection closed + ws.onclose = (web.CloseEvent event) { + final code = event.code; + final reason = event.reason; + final wasClean = event.wasClean; + debugPrint('=== [WebConvexClient] WebSocket closed ==='); + debugPrint('=== [WebConvexClient] Close code: $code, reason: "$reason", wasClean: $wasClean ==='); + _updateConnectionState(WebSocketConnectionState.connecting); + + // Attempt reconnection if not disposed + if (!_isDisposed) { + _scheduleReconnect(); + } + }.toJS; + + // Connection error + ws.onerror = (web.Event event) { + debugPrint('ERROR: [WebConvexClient] WebSocket error occurred'); + debugPrint('ERROR: [WebConvexClient] Event type: ${event.type}'); + _updateConnectionState(WebSocketConnectionState.connecting); + }.toJS; + + // Message received + ws.onmessage = (web.MessageEvent event) { + final data = event.data; + + // Convert JSAny? to String + final dataString = (data as JSString?)?.toDart; + if (dataString != null) { + _handleMessage(dataString); + } else { + debugPrint('WARNING: [WebConvexClient] Received non-string message'); + } + }.toJS; + } + + /// Handles incoming WebSocket messages. + void _handleMessage(String data) { + try { + debugPrint('=== [WebConvexClient] RAW MESSAGE: $data ==='); + + final message = jsonDecode(data) as Map; + final type = message['type'] as String?; + final id = message['id'] as String?; + + debugPrint('=== [WebConvexClient] Received message type: $type, id: $id ==='); + + switch (type) { + case 'Transition': + // Query subscription updates + _handleTransition(message); + break; + + case 'MutationResponse': + _handleMutationResponse(message); + break; + + case 'ActionResponse': + _handleActionResponse(message); + break; + + case 'Ping': + // Respond to server ping + _sendPong(); + break; + + case 'FatalError': + _handleFatalError(message); + break; + + case 'AuthError': + _handleAuthError(message); + break; + + default: + debugPrint('WARNING: [WebConvexClient] Unknown message type: $type'); + } + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Failed to parse message: $e'); + } + } + + /// Handles Transition messages (query subscription updates). + void _handleTransition(Map message) { + final modifications = message['modifications'] as List?; + if (modifications == null) return; + + for (final mod in modifications) { + final queryId = mod['queryId']?.toString(); + if (queryId == null) continue; + + final subscription = _subscriptions[queryId]; + if (subscription == null) continue; + + final value = mod['value']; + if (value != null) { + final valueJson = jsonEncode(value); + subscription.onUpdate(valueJson); + } + } + } + + /// Handles MutationResponse messages. + void _handleMutationResponse(Map message) { + final requestId = message['requestId'] as int?; + if (requestId == null) return; + + final completer = _pendingRequests.remove(requestId); + if (completer == null) return; + + final result = message['result']; + if (result != null) { + final resultJson = jsonEncode(result); + completer.complete(resultJson); + } else { + completer.completeError(Exception('No result in mutation response')); + } + } + + /// Handles ActionResponse messages. + void _handleActionResponse(Map message) { + final requestId = message['requestId'] as int?; + if (requestId == null) return; + + final completer = _pendingRequests.remove(requestId); + if (completer == null) return; + + final result = message['result']; + if (result != null) { + final resultJson = jsonEncode(result); + completer.complete(resultJson); + } else { + completer.completeError(Exception('No result in action response')); + } + } + + /// Handles FatalError messages. + void _handleFatalError(Map message) { + final error = message['error'] as String? ?? 'Unknown fatal error'; + debugPrint('FATAL ERROR: [WebConvexClient] $error'); + + // Close connection on fatal error + _ws?.close(); + } + + /// Handles AuthError messages. + void _handleAuthError(Map message) { + final error = message['error'] as String? ?? 'Authentication error'; + debugPrint('AUTH ERROR: [WebConvexClient] $error'); + + // Clear auth and notify + _authStateController.add(false); + } + + /// Sends Pong response to server Ping. + void _sendPong() { + try { + _sendMessage({ + 'type': 'Event', + 'eventType': 'Pong', // Required field + 'event': null, // Required field (can be null) + }); + debugPrint('=== [WebConvexClient] Sent Pong ==='); + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Failed to send Pong: $e'); + } + } + + /// Sends Connect handshake message. + void _sendConnectMessage() { + try { + // Generate or reuse session ID (must be valid UUID format) + _sessionId ??= _generateUuid(); + + _sendMessage({ + 'type': 'Connect', + 'sessionId': _sessionId, + 'maxObservedTimestamp': null, + 'connectionCount': _reconnectAttempts + 1, + 'lastCloseReason': null, // Required field + 'clientTs': DateTime.now().millisecondsSinceEpoch, // Required field + }); + debugPrint('=== [WebConvexClient] Sent Connect handshake ==='); + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Failed to send Connect: $e'); + } + } + + /// Generates a RFC 4122 compliant UUID v4 string. + String _generateUuid() { + // UUID v4 format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx + // Where 4 = version 4, y = variant bits (8, 9, A, or B) + final random = math.Random(); + + // Generate random values for each segment + final segment1 = random.nextInt(0x100000000); // 32 bits = 8 hex chars + final segment2 = random.nextInt(0x10000); // 16 bits = 4 hex chars + final segment3 = random.nextInt(0x10000); // 16 bits = 4 hex chars (we'll set version) + final segment4 = random.nextInt(0x10000); // 16 bits = 4 hex chars (we'll set variant) + final segment5a = random.nextInt(0x100000000); // 32 bits = 8 hex chars + final segment5b = random.nextInt(0x10000); // 16 bits = 4 hex chars + + // Set version 4 (bits 12-15 of segment3 = 0100) + final version4 = (segment3 & 0x0FFF) | 0x4000; + + // Set variant bits (bits 14-15 of segment4 = 10) + final variant = (segment4 & 0x3FFF) | 0x8000; + + // Combine segment5 parts into 12 hex digits + final segment5 = '${segment5a.toRadixString(16).padLeft(8, '0')}${segment5b.toRadixString(16).padLeft(4, '0')}'; + + return '${segment1.toRadixString(16).padLeft(8, '0')}-' + '${segment2.toRadixString(16).padLeft(4, '0')}-' + '${version4.toRadixString(16).padLeft(4, '0')}-' + '${variant.toRadixString(16).padLeft(4, '0')}-' + '$segment5'; + } + + /// Updates connection state and emits to stream. + void _updateConnectionState(WebSocketConnectionState newState) { + if (_currentConnectionState != newState) { + debugPrint('=== [WebConvexClient] State transition: ${_currentConnectionState.name} → ${newState.name} ==='); + _currentConnectionState = newState; + _connectionStateController.add(newState); + } + } + + /// Schedules a reconnection attempt with exponential backoff. + void _scheduleReconnect() { + if (_isDisposed) return; + + _reconnectTimer?.cancel(); + + if (_reconnectAttempts >= _maxReconnectAttempts) { + debugPrint('ERROR: [WebConvexClient] Max reconnection attempts reached'); + return; + } + + // Exponential backoff: 1s, 2s, 4s, 8s, 16s, 32s (max) + final delay = _baseReconnectDelay * (1 << _reconnectAttempts.clamp(0, 5)); + _reconnectAttempts++; + + debugPrint('=== [WebConvexClient] Scheduling reconnect attempt $_reconnectAttempts in ${delay.inSeconds}s ==='); + + _reconnectTimer = Timer(delay, () { + debugPrint('=== [WebConvexClient] Executing reconnect attempt $_reconnectAttempts ==='); + _connect(); + }); + } + + /// Generates a unique message ID. + int _generateMessageId() { + return _messageIdCounter++; + } + + /// Sends a message over WebSocket. + void _sendMessage(Map message) { + final ws = _ws; + if (ws == null || ws.readyState != web.WebSocket.OPEN) { + throw StateError('WebSocket not connected'); + } + + final messageJson = jsonEncode(message); + debugPrint('=== [WebConvexClient] SENDING: $messageJson ==='); + ws.send(messageJson.toJS); + + debugPrint('=== [WebConvexClient] Sent message: ${message['type']} (id: ${message['id']}) ==='); + } + + /// Sends authentication message. + void _sendAuthMessage(String token) { + try { + // Send Authenticate message (Convex protocol) + _sendMessage({ + 'type': 'Authenticate', + 'token': token, + }); + debugPrint('=== [WebConvexClient] Auth token sent ==='); + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Failed to send auth: $e'); + } + } + + // ============================================================================ + // IConvexClient Implementation - Core Operations + // ============================================================================ + + @override + Future query(String name, Map args) async { + // Queries in Convex protocol use ModifyQuerySet (like subscriptions) + // We subscribe, wait for first result, then unsubscribe + final queryId = _queryIdCounter++; + final queryIdStr = queryId.toString(); + final completer = Completer(); + + // Create temporary subscription for one-shot query + final subscription = _WebSubscription( + id: queryIdStr, + onUpdate: (value) { + if (!completer.isCompleted) { + completer.complete(value); + // Auto-unsubscribe after getting result + _unsubscribe(queryIdStr); + } + }, + onError: (message, value) { + if (!completer.isCompleted) { + completer.completeError(Exception(message)); + _subscriptions.remove(queryIdStr); + } + }, + ); + _subscriptions[queryIdStr] = subscription; + + try { + // Send ModifyQuerySet with Add (Convex protocol for queries) + final baseVersion = _querySetVersion; + final newVersion = ++_querySetVersion; + + _sendMessage({ + 'type': 'ModifyQuerySet', + 'baseVersion': baseVersion, + 'newVersion': newVersion, + 'modifications': [ + { + 'type': 'Add', + 'queryId': queryId, + 'udfPath': name, + 'args': [args], // Args must be array + } + ], + }); + + return await completer.future.timeout( + config.operationTimeout, + onTimeout: () { + _subscriptions.remove(queryIdStr); + throw TimeoutException('Query timeout: $name'); + }, + ); + } catch (e) { + _subscriptions.remove(queryIdStr); + rethrow; + } + } + + @override + Future mutation({ + required String name, + required Map args, + }) async { + final requestId = _generateMessageId(); + final completer = Completer(); + _pendingRequests[requestId] = completer; + + try { + // Send Mutation message (Convex protocol) + _sendMessage({ + 'type': 'Mutation', + 'requestId': requestId, + 'udfPath': name, // Use udfPath instead of name + 'args': [args], // Args must be array, not object + }); + + return await completer.future.timeout( + config.operationTimeout, + onTimeout: () { + _pendingRequests.remove(requestId); + throw TimeoutException('Mutation timeout: $name'); + }, + ); + } catch (e) { + _pendingRequests.remove(requestId); + rethrow; + } + } + + @override + Future action({ + required String name, + required Map args, + }) async { + final requestId = _generateMessageId(); + final completer = Completer(); + _pendingRequests[requestId] = completer; + + try { + // Send Action message (Convex protocol) + _sendMessage({ + 'type': 'Action', + 'requestId': requestId, + 'udfPath': name, // Use udfPath instead of name + 'args': [args], // Args must be array, not object + }); + + return await completer.future.timeout( + config.operationTimeout, + onTimeout: () { + _pendingRequests.remove(requestId); + throw TimeoutException('Action timeout: $name'); + }, + ); + } catch (e) { + _pendingRequests.remove(requestId); + rethrow; + } + } + + @override + Future subscribe({ + required String name, + required Map args, + required void Function(String) onUpdate, + required void Function(String, String?) onError, + }) async { + // Use incrementing query ID (Convex protocol requirement) + final queryId = _queryIdCounter++; + final queryIdStr = queryId.toString(); + + // Create subscription record + final subscription = _WebSubscription( + id: queryIdStr, + onUpdate: onUpdate, + onError: onError, + ); + _subscriptions[queryIdStr] = subscription; + + try { + // Send ModifyQuerySet with Add modification (Convex protocol) + final baseVersion = _querySetVersion; + final newVersion = ++_querySetVersion; + + _sendMessage({ + 'type': 'ModifyQuerySet', + 'baseVersion': baseVersion, + 'newVersion': newVersion, + 'modifications': [ + { + 'type': 'Add', + 'queryId': queryId, + 'udfPath': name, // Use udfPath instead of name + 'args': [args], // Args must be array, not object + } + ], + }); + + debugPrint('=== [WebConvexClient] Subscription created: queryId=$queryId ==='); + + // Return handle for cancellation + return _WebSubscriptionHandle( + onCancel: () { + _unsubscribe(queryIdStr); + }, + ); + } catch (e) { + _subscriptions.remove(queryIdStr); + rethrow; + } + } + + /// Unsubscribes from a subscription. + void _unsubscribe(String queryIdStr) { + final subscription = _subscriptions.remove(queryIdStr); + if (subscription == null) return; + + debugPrint('=== [WebConvexClient] Unsubscribing: queryId=$queryIdStr ==='); + + try { + final queryId = int.tryParse(queryIdStr); + if (queryId == null) return; + + // Send ModifyQuerySet with Remove modification (Convex protocol) + final baseVersion = _querySetVersion; + final newVersion = ++_querySetVersion; + + _sendMessage({ + 'type': 'ModifyQuerySet', + 'baseVersion': baseVersion, + 'newVersion': newVersion, + 'modifications': [ + { + 'type': 'Remove', + 'queryId': queryId, + } + ], + }); + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Failed to send unsubscribe: $e'); + } + } + + // ============================================================================ + // IConvexClient Implementation - Authentication + // ============================================================================ + + @override + Future setAuth({required String? token}) async { + _currentAuthToken = token; + + if (token != null) { + _sendAuthMessage(token); + _authStateController.add(true); + } else { + _sendAuthMessage(''); // Clear auth + _authStateController.add(false); + } + } + + @override + Future setAuthWithRefresh({ + required Future Function() tokenFetcher, + void Function(bool isAuthenticated)? onAuthChange, + }) async { + // TODO: Implement token refresh for web + // For now, just fetch token once and set it + final token = await tokenFetcher(); + await setAuth(token: token); + + if (onAuthChange != null) { + onAuthChange(token != null); + } + + // Return a simple auth handle (no auto-refresh yet) + return _WebAuthHandle( + isAuth: token != null, + onDispose: () async { + await setAuth(token: null); + }, + ); + } + + @override + Future clearAuth() async { + await setAuth(token: null); + } + + @override + Stream get authState => _authStateController.stream; + + @override + bool get isAuthenticated => _currentAuthToken != null; + + // ============================================================================ + // IConvexClient Implementation - Connection Management + // ============================================================================ + + @override + Stream get connectionState => + _connectionStateController.stream; + + @override + WebSocketConnectionState get currentConnectionState => _currentConnectionState; + + @override + bool get isConnected => + _currentConnectionState == WebSocketConnectionState.connected; + + @override + @Deprecated('Use connectionState stream for real-time monitoring') + Future checkConnection() async { + if (config.healthCheckQuery == null) { + throw StateError( + 'No health check query configured. ' + 'Set healthCheckQuery in ConvexConfig or use a real query.', + ); + } + + try { + await query(config.healthCheckQuery!, {}); + return ConnectionStatus.connected; + } on TimeoutException { + return ConnectionStatus.timeout; + } catch (e) { + return ConnectionStatus.error; + } + } + + @override + Future reconnect() async { + debugPrint('=== [WebConvexClient] Manual reconnect requested ==='); + + // Close existing connection if any + _ws?.close(); + _ws = null; + + // Reset reconnection counter for manual reconnect + _reconnectAttempts = 0; + + // Attempt connection + try { + await _connect(); + + // Wait a bit for connection to establish + await Future.delayed(const Duration(seconds: 2)); + + return isConnected; + } catch (e) { + debugPrint('ERROR: [WebConvexClient] Manual reconnect failed: $e'); + return false; + } + } + + // ============================================================================ + // IConvexClient Implementation - Lifecycle Management + // ============================================================================ + + @override + Stream get lifecycleEvents => _lifecycleController.stream; + + // ============================================================================ + // IConvexClient Implementation - Resource Management + // ============================================================================ + + @override + void dispose() { + if (_isDisposed) return; + + debugPrint('=== [WebConvexClient] Disposing client ==='); + _isDisposed = true; + + // Cancel reconnection timer + _reconnectTimer?.cancel(); + + // Close WebSocket + _ws?.close(); + _ws = null; + + // Dispose lifecycle observer + _lifecycleObserver.dispose(); + + // Close streams + _authStateController.close(); + _lifecycleController.close(); + _connectionStateController.close(); + + // Clear pending requests and subscriptions + _pendingRequests.clear(); + _subscriptions.clear(); + + debugPrint('=== [WebConvexClient] Client disposed ==='); + } +} + +/// Internal subscription record for web client. +class _WebSubscription { + final String id; + final void Function(String) onUpdate; + final void Function(String, String?) onError; + + _WebSubscription({ + required this.id, + required this.onUpdate, + required this.onError, + }); +} + +/// Web implementation of SubscriptionHandle. +class _WebSubscriptionHandle implements SubscriptionHandle { + final void Function() onCancel; + bool _isCancelled = false; + + _WebSubscriptionHandle({required this.onCancel}); + + @override + void cancel() { + if (!_isCancelled) { + _isCancelled = true; + onCancel(); + } + } + + @override + void dispose() { + cancel(); + } + + @override + bool get isDisposed => _isCancelled; +} + +/// Web implementation of AuthHandle. +class _WebAuthHandle implements AuthHandle { + final bool isAuth; + final Future Function() onDispose; + bool _isDisposed = false; + + _WebAuthHandle({ + required this.isAuth, + required this.onDispose, + }); + + @override + bool isAuthenticated() => isAuth && !_isDisposed; + + @override + void dispose() { + if (!_isDisposed) { + _isDisposed = true; + onDispose(); + } + } + + @override + bool get isDisposed => _isDisposed; +} diff --git a/third_party/convex_flutter/lib/src/rust/frb_generated.dart b/third_party/convex_flutter/lib/src/rust/frb_generated.dart new file mode 100644 index 00000000..47d8eb5e --- /dev/null +++ b/third_party/convex_flutter/lib/src/rust/frb_generated.dart @@ -0,0 +1,2291 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'frb_generated.io.dart' + if (dart.library.js_interop) 'frb_generated.web.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; + +/// Main entrypoint of the Rust API +class RustLib extends BaseEntrypoint { + @internal + static final instance = RustLib._(); + + RustLib._(); + + /// Initialize flutter_rust_bridge + static Future init({ + RustLibApi? api, + BaseHandler? handler, + ExternalLibrary? externalLibrary, + bool forceSameCodegenVersion = true, + }) async { + await instance.initImpl( + api: api, + handler: handler, + externalLibrary: externalLibrary, + forceSameCodegenVersion: forceSameCodegenVersion, + ); + } + + /// Initialize flutter_rust_bridge in mock mode. + /// No libraries for FFI are loaded. + static void initMock({required RustLibApi api}) { + instance.initMockImpl(api: api); + } + + /// Dispose flutter_rust_bridge + /// + /// The call to this function is optional, since flutter_rust_bridge (and everything else) + /// is automatically disposed when the app stops. + static void dispose() => instance.disposeImpl(); + + @override + ApiImplConstructor get apiImplConstructor => + RustLibApiImpl.new; + + @override + WireConstructor get wireConstructor => + RustLibWire.fromExternalLibrary; + + @override + Future executeRustInitializers() async {} + + @override + ExternalLibraryLoaderConfig get defaultExternalLibraryLoaderConfig => + kDefaultExternalLibraryLoaderConfig; + + @override + String get codegenVersion => '2.11.1'; + + @override + int get rustContentHash => -829523767; + + static const kDefaultExternalLibraryLoaderConfig = + ExternalLibraryLoaderConfig( + stem: 'convex_flutter', + ioDirectory: 'rust/target/release/', + webPrefix: 'pkg/', + ); +} + +abstract class RustLibApi extends BaseApi { + void crateAuthHandleDispose({required AuthHandle that}); + + bool crateAuthHandleIsAuthenticated({required AuthHandle that}); + + Future crateCallbackSubscriberDartFnOnError({ + required CallbackSubscriberDartFn that, + required String message, + String? value, + }); + + Future crateCallbackSubscriberDartFnOnUpdate({ + required CallbackSubscriberDartFn that, + required String value, + }); + + Future crateCallbackSubscriberOnError({ + required CallbackSubscriber that, + required String message, + String? value, + }); + + Future crateCallbackSubscriberOnUpdate({ + required CallbackSubscriber that, + required String value, + }); + + Future crateMobileConvexClientAction({ + required MobileConvexClient that, + required String name, + required Map args, + }); + + Future crateMobileConvexClientMutation({ + required MobileConvexClient that, + required String name, + required Map args, + }); + + MobileConvexClient crateMobileConvexClientNew({ + required String deploymentUrl, + required String clientId, + }); + + Future crateMobileConvexClientOnWebsocketStateChange({ + required MobileConvexClient that, + required FutureOr Function(WebSocketConnectionState) onStateChange, + }); + + Future crateMobileConvexClientQuery({ + required MobileConvexClient that, + required String name, + required Map args, + }); + + Future crateMobileConvexClientReconnectNow({ + required MobileConvexClient that, + required String reason, + }); + + Future crateMobileConvexClientSetAuth({ + required MobileConvexClient that, + String? token, + }); + + Future crateMobileConvexClientSetAuthWithRefresh({ + required MobileConvexClient that, + required FutureOr Function() fetchToken, + required FutureOr Function(bool) onAuthChange, + }); + + Future crateMobileConvexClientSubscribe({ + required MobileConvexClient that, + required String name, + required Map args, + required FutureOr Function(String) onUpdate, + required FutureOr Function(String, String?) onError, + }); + + void crateSubscriptionHandleCancel({required SubscriptionHandle that}); + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_AuthHandle; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_AuthHandle; + + CrossPlatformFinalizerArg get rust_arc_decrement_strong_count_AuthHandlePtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_CallbackSubscriber; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_CallbackSubscriber; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_CallbackSubscriberDartFn; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_CallbackSubscriberDartFn; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberDartFnPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_MobileConvexClient; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_MobileConvexClient; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_MobileConvexClientPtr; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_SubscriptionHandle; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_SubscriptionHandle; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_SubscriptionHandlePtr; +} + +class RustLibApiImpl extends RustLibApiImplPlatform implements RustLibApi { + RustLibApiImpl({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + @override + void crateAuthHandleDispose({required AuthHandle that}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + that, + serializer, + ); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 1)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateAuthHandleDisposeConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateAuthHandleDisposeConstMeta => + const TaskConstMeta(debugName: "AuthHandle_dispose", argNames: ["that"]); + + @override + bool crateAuthHandleIsAuthenticated({required AuthHandle that}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + that, + serializer, + ); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 2)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_bool, + decodeErrorData: null, + ), + constMeta: kCrateAuthHandleIsAuthenticatedConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateAuthHandleIsAuthenticatedConstMeta => + const TaskConstMeta( + debugName: "AuthHandle_is_authenticated", + argNames: ["that"], + ); + + @override + Future crateCallbackSubscriberDartFnOnError({ + required CallbackSubscriberDartFn that, + required String message, + String? value, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + that, + serializer, + ); + sse_encode_String(message, serializer); + sse_encode_opt_String(value, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 3, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateCallbackSubscriberDartFnOnErrorConstMeta, + argValues: [that, message, value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateCallbackSubscriberDartFnOnErrorConstMeta => + const TaskConstMeta( + debugName: "CallbackSubscriberDartFn_on_error", + argNames: ["that", "message", "value"], + ); + + @override + Future crateCallbackSubscriberDartFnOnUpdate({ + required CallbackSubscriberDartFn that, + required String value, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + that, + serializer, + ); + sse_encode_String(value, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 4, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateCallbackSubscriberDartFnOnUpdateConstMeta, + argValues: [that, value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateCallbackSubscriberDartFnOnUpdateConstMeta => + const TaskConstMeta( + debugName: "CallbackSubscriberDartFn_on_update", + argNames: ["that", "value"], + ); + + @override + Future crateCallbackSubscriberOnError({ + required CallbackSubscriber that, + required String message, + String? value, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + that, + serializer, + ); + sse_encode_String(message, serializer); + sse_encode_opt_String(value, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 5, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateCallbackSubscriberOnErrorConstMeta, + argValues: [that, message, value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateCallbackSubscriberOnErrorConstMeta => + const TaskConstMeta( + debugName: "CallbackSubscriber_on_error", + argNames: ["that", "message", "value"], + ); + + @override + Future crateCallbackSubscriberOnUpdate({ + required CallbackSubscriber that, + required String value, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + that, + serializer, + ); + sse_encode_String(value, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 6, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateCallbackSubscriberOnUpdateConstMeta, + argValues: [that, value], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateCallbackSubscriberOnUpdateConstMeta => + const TaskConstMeta( + debugName: "CallbackSubscriber_on_update", + argNames: ["that", "value"], + ); + + @override + Future crateMobileConvexClientAction({ + required MobileConvexClient that, + required String name, + required Map args, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_String(name, serializer); + sse_encode_Map_String_String_None(args, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 7, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientActionConstMeta, + argValues: [that, name, args], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientActionConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_action", + argNames: ["that", "name", "args"], + ); + + @override + Future crateMobileConvexClientMutation({ + required MobileConvexClient that, + required String name, + required Map args, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_String(name, serializer); + sse_encode_Map_String_String_None(args, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 8, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientMutationConstMeta, + argValues: [that, name, args], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientMutationConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_mutation", + argNames: ["that", "name", "args"], + ); + + @override + MobileConvexClient crateMobileConvexClientNew({ + required String deploymentUrl, + required String clientId, + }) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_String(deploymentUrl, serializer); + sse_encode_String(clientId, serializer); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 9)!; + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient, + decodeErrorData: null, + ), + constMeta: kCrateMobileConvexClientNewConstMeta, + argValues: [deploymentUrl, clientId], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientNewConstMeta => const TaskConstMeta( + debugName: "MobileConvexClient_new", + argNames: ["deploymentUrl", "clientId"], + ); + + @override + Future crateMobileConvexClientOnWebsocketStateChange({ + required MobileConvexClient that, + required FutureOr Function(WebSocketConnectionState) onStateChange, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + onStateChange, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 10, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientOnWebsocketStateChangeConstMeta, + argValues: [that, onStateChange], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientOnWebsocketStateChangeConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_on_websocket_state_change", + argNames: ["that", "onStateChange"], + ); + + @override + Future crateMobileConvexClientQuery({ + required MobileConvexClient that, + required String name, + required Map args, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_String(name, serializer); + sse_encode_Map_String_String_None(args, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 11, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_String, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientQueryConstMeta, + argValues: [that, name, args], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientQueryConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_query", + argNames: ["that", "name", "args"], + ); + + @override + Future crateMobileConvexClientReconnectNow({ + required MobileConvexClient that, + required String reason, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_String(reason, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 12, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientReconnectNowConstMeta, + argValues: [that, reason], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientReconnectNowConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_reconnect_now", + argNames: ["that", "reason"], + ); + + @override + Future crateMobileConvexClientSetAuth({ + required MobileConvexClient that, + String? token, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_opt_String(token, serializer); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 13, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientSetAuthConstMeta, + argValues: [that, token], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientSetAuthConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_set_auth", + argNames: ["that", "token"], + ); + + @override + Future crateMobileConvexClientSetAuthWithRefresh({ + required MobileConvexClient that, + required FutureOr Function() fetchToken, + required FutureOr Function(bool) onAuthChange, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_DartFn_Inputs__Output_opt_String_AnyhowException( + fetchToken, + serializer, + ); + sse_encode_DartFn_Inputs_bool_Output_unit_AnyhowException( + onAuthChange, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 14, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientSetAuthWithRefreshConstMeta, + argValues: [that, fetchToken, onAuthChange], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientSetAuthWithRefreshConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_set_auth_with_refresh", + argNames: ["that", "fetchToken", "onAuthChange"], + ); + + @override + Future crateMobileConvexClientSubscribe({ + required MobileConvexClient that, + required String name, + required Map args, + required FutureOr Function(String) onUpdate, + required FutureOr Function(String, String?) onError, + }) { + return handler.executeNormal( + NormalTask( + callFfi: (port_) { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + that, + serializer, + ); + sse_encode_String(name, serializer); + sse_encode_Map_String_String_None(args, serializer); + sse_encode_DartFn_Inputs_String_Output_unit_AnyhowException( + onUpdate, + serializer, + ); + sse_encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + onError, + serializer, + ); + pdeCallFfi( + generalizedFrbRustBinding, + serializer, + funcId: 15, + port: port_, + ); + }, + codec: SseCodec( + decodeSuccessData: + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle, + decodeErrorData: sse_decode_client_error, + ), + constMeta: kCrateMobileConvexClientSubscribeConstMeta, + argValues: [that, name, args, onUpdate, onError], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateMobileConvexClientSubscribeConstMeta => + const TaskConstMeta( + debugName: "MobileConvexClient_subscribe", + argNames: ["that", "name", "args", "onUpdate", "onError"], + ); + + @override + void crateSubscriptionHandleCancel({required SubscriptionHandle that}) { + return handler.executeSync( + SyncTask( + callFfi: () { + final serializer = SseSerializer(generalizedFrbRustBinding); + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + that, + serializer, + ); + return pdeCallFfi(generalizedFrbRustBinding, serializer, funcId: 16)!; + }, + codec: SseCodec( + decodeSuccessData: sse_decode_unit, + decodeErrorData: null, + ), + constMeta: kCrateSubscriptionHandleCancelConstMeta, + argValues: [that], + apiImpl: this, + ), + ); + } + + TaskConstMeta get kCrateSubscriptionHandleCancelConstMeta => + const TaskConstMeta( + debugName: "SubscriptionHandle_cancel", + argNames: ["that"], + ); + + Future Function(int, dynamic) + encode_DartFn_Inputs_String_Output_unit_AnyhowException( + FutureOr Function(String) raw, + ) { + return (callId, rawArg0) async { + final arg0 = dco_decode_String(rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic, dynamic) + encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + FutureOr Function(String, String?) raw, + ) { + return (callId, rawArg0, rawArg1) async { + final arg0 = dco_decode_String(rawArg0); + final arg1 = dco_decode_opt_String(rawArg1); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0, arg1)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int) + encode_DartFn_Inputs__Output_opt_String_AnyhowException( + FutureOr Function() raw, + ) { + return (callId) async { + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw()); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_opt_String(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic) + encode_DartFn_Inputs_bool_Output_unit_AnyhowException( + FutureOr Function(bool) raw, + ) { + return (callId, rawArg0) async { + final arg0 = dco_decode_bool(rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + Future Function(int, dynamic) + encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + FutureOr Function(WebSocketConnectionState) raw, + ) { + return (callId, rawArg0) async { + final arg0 = dco_decode_web_socket_connection_state(rawArg0); + + Box? rawOutput; + Box? rawError; + try { + rawOutput = Box(await raw(arg0)); + } catch (e, s) { + rawError = Box(AnyhowException("$e\n\n$s")); + } + + final serializer = SseSerializer(generalizedFrbRustBinding); + assert((rawOutput != null) ^ (rawError != null)); + if (rawOutput != null) { + serializer.buffer.putUint8(0); + sse_encode_unit(rawOutput.value, serializer); + } else { + serializer.buffer.putUint8(1); + sse_encode_AnyhowException(rawError!.value, serializer); + } + final output = serializer.intoRaw(); + + generalizedFrbRustBinding.dartFnDeliverOutput( + callId: callId, + ptr: output.ptr, + rustVecLen: output.rustVecLen, + dataLen: output.dataLen, + ); + }; + } + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_AuthHandle => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_AuthHandle => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_CallbackSubscriber => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_CallbackSubscriber => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_CallbackSubscriberDartFn => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_CallbackSubscriberDartFn => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_MobileConvexClient => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_MobileConvexClient => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient; + + RustArcIncrementStrongCountFnType + get rust_arc_increment_strong_count_SubscriptionHandle => wire + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle; + + RustArcDecrementStrongCountFnType + get rust_arc_decrement_strong_count_SubscriptionHandle => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AnyhowException(raw as String); + } + + @protected + AuthHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AuthHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriber + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalDcoDecode( + raw as List, + ); + } + + @protected + MobileConvexClient + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalDcoDecode(raw as List); + } + + @protected + SubscriptionHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + AuthHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AuthHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriber + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalDcoDecode( + raw as List, + ); + } + + @protected + MobileConvexClient + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalDcoDecode(raw as List); + } + + @protected + SubscriptionHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + FutureOr Function(String) + dco_decode_DartFn_Inputs_String_Output_unit_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(String, String?) + dco_decode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_opt_String_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(bool) + dco_decode_DartFn_Inputs_bool_Output_unit_AnyhowException(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + FutureOr Function(WebSocketConnectionState) + dco_decode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(''); + } + + @protected + Object dco_decode_DartOpaque(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return decodeDartOpaque(raw, generalizedFrbRustBinding); + } + + @protected + Map dco_decode_Map_String_String_None(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return Map.fromEntries( + dco_decode_list_record_string_string( + raw, + ).map((e) => MapEntry(e.$1, e.$2)), + ); + } + + @protected + AuthHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return AuthHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriber + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalDcoDecode(raw as List); + } + + @protected + CallbackSubscriberDartFn + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalDcoDecode( + raw as List, + ); + } + + @protected + MobileConvexClient + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalDcoDecode(raw as List); + } + + @protected + SubscriptionHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalDcoDecode(raw as List); + } + + @protected + String dco_decode_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as String; + } + + @protected + QuerySubscriber dco_decode_TraitDef_QuerySubscriber(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + throw UnimplementedError(); + } + + @protected + bool dco_decode_bool(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as bool; + } + + @protected + ClientError dco_decode_client_error(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + switch (raw[0]) { + case 0: + return ClientError_InternalError(msg: dco_decode_String(raw[1])); + case 1: + return ClientError_ConvexError(data: dco_decode_String(raw[1])); + case 2: + return ClientError_ServerError(msg: dco_decode_String(raw[1])); + default: + throw Exception("unreachable"); + } + } + + @protected + int dco_decode_i_32(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + PlatformInt64 dco_decode_isize(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeI64(raw); + } + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as Uint8List; + } + + @protected + List<(String, String)> dco_decode_list_record_string_string(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return (raw as List).map(dco_decode_record_string_string).toList(); + } + + @protected + String? dco_decode_opt_String(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw == null ? null : dco_decode_String(raw); + } + + @protected + (String, String) dco_decode_record_string_string(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + final arr = raw as List; + if (arr.length != 2) { + throw Exception('Expected 2 elements, got ${arr.length}'); + } + return (dco_decode_String(arr[0]), dco_decode_String(arr[1])); + } + + @protected + int dco_decode_u_8(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return raw as int; + } + + @protected + void dco_decode_unit(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return; + } + + @protected + BigInt dco_decode_usize(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return dcoDecodeU64(raw); + } + + @protected + WebSocketConnectionState dco_decode_web_socket_connection_state(dynamic raw) { + // Codec=Dco (DartCObject based), see doc to use other codecs + return WebSocketConnectionState.values[raw as int]; + } + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_String(deserializer); + return AnyhowException(inner); + } + + @protected + AuthHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AuthHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriber + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + MobileConvexClient + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + SubscriptionHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + AuthHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AuthHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriber + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + MobileConvexClient + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + SubscriptionHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_isize(deserializer); + return decodeDartOpaque(inner, generalizedFrbRustBinding); + } + + @protected + Map sse_decode_Map_String_String_None( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_record_string_string(deserializer); + return Map.fromEntries(inner.map((e) => MapEntry(e.$1, e.$2))); + } + + @protected + AuthHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return AuthHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriber + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + CallbackSubscriberDartFn + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return CallbackSubscriberDartFnImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + MobileConvexClient + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return MobileConvexClientImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + SubscriptionHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + return SubscriptionHandleImpl.frbInternalSseDecode( + sse_decode_usize(deserializer), + sse_decode_i_32(deserializer), + ); + } + + @protected + String sse_decode_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_list_prim_u_8_strict(deserializer); + return utf8.decoder.convert(inner); + } + + @protected + bool sse_decode_bool(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8() != 0; + } + + @protected + ClientError sse_decode_client_error(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var tag_ = sse_decode_i_32(deserializer); + switch (tag_) { + case 0: + var var_msg = sse_decode_String(deserializer); + return ClientError_InternalError(msg: var_msg); + case 1: + var var_data = sse_decode_String(deserializer); + return ClientError_ConvexError(data: var_data); + case 2: + var var_msg = sse_decode_String(deserializer); + return ClientError_ServerError(msg: var_msg); + default: + throw UnimplementedError(''); + } + } + + @protected + int sse_decode_i_32(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getInt32(); + } + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getPlatformInt64(); + } + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + var len_ = sse_decode_i_32(deserializer); + return deserializer.buffer.getUint8List(len_); + } + + @protected + List<(String, String)> sse_decode_list_record_string_string( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + + var len_ = sse_decode_i_32(deserializer); + var ans_ = <(String, String)>[]; + for (var idx_ = 0; idx_ < len_; ++idx_) { + ans_.add(sse_decode_record_string_string(deserializer)); + } + return ans_; + } + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + if (sse_decode_bool(deserializer)) { + return (sse_decode_String(deserializer)); + } else { + return null; + } + } + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var var_field0 = sse_decode_String(deserializer); + var var_field1 = sse_decode_String(deserializer); + return (var_field0, var_field1); + } + + @protected + int sse_decode_u_8(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getUint8(); + } + + @protected + void sse_decode_unit(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + return deserializer.buffer.getBigUint64(); + } + + @protected + WebSocketConnectionState sse_decode_web_socket_connection_state( + SseDeserializer deserializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + var inner = sse_decode_i_32(deserializer); + return WebSocketConnectionState.values[inner]; + } + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.message, serializer); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AuthHandleImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberDartFnImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MobileConvexClientImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as SubscriptionHandleImpl).frbInternalSseEncode(move: true), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AuthHandleImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberDartFnImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MobileConvexClientImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as SubscriptionHandleImpl).frbInternalSseEncode(move: false), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_String_Output_unit_AnyhowException( + FutureOr Function(String) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_String_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + FutureOr Function(String, String?) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs__Output_opt_String_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs__Output_opt_String_AnyhowException(self), + serializer, + ); + } + + @protected + void sse_encode_DartFn_Inputs_bool_Output_unit_AnyhowException( + FutureOr Function(bool) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_bool_Output_unit_AnyhowException(self), + serializer, + ); + } + + @protected + void + sse_encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + FutureOr Function(WebSocketConnectionState) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_DartOpaque( + encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + self, + ), + serializer, + ); + } + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_isize( + PlatformPointerUtil.ptrToPlatformInt64( + encodeDartOpaque( + self, + portManager.dartHandlerPort, + generalizedFrbRustBinding, + ), + ), + serializer, + ); + } + + @protected + void sse_encode_Map_String_String_None( + Map self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_record_string_string( + self.entries.map((e) => (e.key, e.value)).toList(), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as AuthHandleImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as CallbackSubscriberDartFnImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as MobileConvexClientImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_usize( + (self as SubscriptionHandleImpl).frbInternalSseEncode(move: null), + serializer, + ); + } + + @protected + void sse_encode_String(String self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_list_prim_u_8_strict(utf8.encoder.convert(self), serializer); + } + + @protected + void sse_encode_bool(bool self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self ? 1 : 0); + } + + @protected + void sse_encode_client_error(ClientError self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + switch (self) { + case ClientError_InternalError(msg: final msg): + sse_encode_i_32(0, serializer); + sse_encode_String(msg, serializer); + case ClientError_ConvexError(data: final data): + sse_encode_i_32(1, serializer); + sse_encode_String(data, serializer); + case ClientError_ServerError(msg: final msg): + sse_encode_i_32(2, serializer); + sse_encode_String(msg, serializer); + } + } + + @protected + void sse_encode_i_32(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putInt32(self); + } + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putPlatformInt64(self); + } + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + serializer.buffer.putUint8List(self); + } + + @protected + void sse_encode_list_record_string_string( + List<(String, String)> self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.length, serializer); + for (final item in self) { + sse_encode_record_string_string(item, serializer); + } + } + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + + sse_encode_bool(self != null, serializer); + if (self != null) { + sse_encode_String(self, serializer); + } + } + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_String(self.$1, serializer); + sse_encode_String(self.$2, serializer); + } + + @protected + void sse_encode_u_8(int self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putUint8(self); + } + + @protected + void sse_encode_unit(void self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + } + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer) { + // Codec=Sse (Serialization based), see doc to use other codecs + serializer.buffer.putBigUint64(self); + } + + @protected + void sse_encode_web_socket_connection_state( + WebSocketConnectionState self, + SseSerializer serializer, + ) { + // Codec=Sse (Serialization based), see doc to use other codecs + sse_encode_i_32(self.index, serializer); + } +} + +@sealed +class AuthHandleImpl extends RustOpaque implements AuthHandle { + // Not to be used by end users + AuthHandleImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + AuthHandleImpl.frbInternalSseDecode(BigInt ptr, int externalSizeOnNative) + : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_AuthHandle, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_AuthHandle, + rustArcDecrementStrongCountPtr: + RustLib.instance.api.rust_arc_decrement_strong_count_AuthHandlePtr, + ); + + /// Disposes the auth session, stopping the token refresh loop and clearing authentication. + void dispose() => RustLib.instance.api.crateAuthHandleDispose(that: this); + + /// Returns whether the user is currently authenticated. + bool isAuthenticated() => + RustLib.instance.api.crateAuthHandleIsAuthenticated(that: this); +} + +@sealed +class CallbackSubscriberDartFnImpl extends RustOpaque + implements CallbackSubscriberDartFn { + // Not to be used by end users + CallbackSubscriberDartFnImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + CallbackSubscriberDartFnImpl.frbInternalSseDecode( + BigInt ptr, + int externalSizeOnNative, + ) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: RustLib + .instance + .api + .rust_arc_increment_strong_count_CallbackSubscriberDartFn, + rustArcDecrementStrongCount: RustLib + .instance + .api + .rust_arc_decrement_strong_count_CallbackSubscriberDartFn, + rustArcDecrementStrongCountPtr: RustLib + .instance + .api + .rust_arc_decrement_strong_count_CallbackSubscriberDartFnPtr, + ); + + Future onError({required String message, String? value}) => + RustLib.instance.api.crateCallbackSubscriberDartFnOnError( + that: this, + message: message, + value: value, + ); + + Future onUpdate({required String value}) => RustLib.instance.api + .crateCallbackSubscriberDartFnOnUpdate(that: this, value: value); +} + +@sealed +class CallbackSubscriberImpl extends RustOpaque implements CallbackSubscriber { + // Not to be used by end users + CallbackSubscriberImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + CallbackSubscriberImpl.frbInternalSseDecode( + BigInt ptr, + int externalSizeOnNative, + ) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_CallbackSubscriber, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_CallbackSubscriber, + rustArcDecrementStrongCountPtr: RustLib + .instance + .api + .rust_arc_decrement_strong_count_CallbackSubscriberPtr, + ); + + Future onError({required String message, String? value}) => + RustLib.instance.api.crateCallbackSubscriberOnError( + that: this, + message: message, + value: value, + ); + + Future onUpdate({required String value}) => RustLib.instance.api + .crateCallbackSubscriberOnUpdate(that: this, value: value); +} + +@sealed +class MobileConvexClientImpl extends RustOpaque implements MobileConvexClient { + // Not to be used by end users + MobileConvexClientImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + MobileConvexClientImpl.frbInternalSseDecode( + BigInt ptr, + int externalSizeOnNative, + ) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_MobileConvexClient, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_MobileConvexClient, + rustArcDecrementStrongCountPtr: RustLib + .instance + .api + .rust_arc_decrement_strong_count_MobileConvexClientPtr, + ); + + /// Executes an action on the Convex backend. + Future action({ + required String name, + required Map args, + }) => RustLib.instance.api.crateMobileConvexClientAction( + that: this, + name: name, + args: args, + ); + + /// Executes a mutation on the Convex backend. + Future mutation({ + required String name, + required Map args, + }) => RustLib.instance.api.crateMobileConvexClientMutation( + that: this, + name: name, + args: args, + ); + + /// Sets up WebSocket connection state change listener. + /// + /// Must be called BEFORE any queries/mutations to capture all state changes. + /// The callback will be invoked whenever the WebSocket transitions between + /// Connected and Connecting states. + /// + /// # Arguments + /// + /// * `on_state_change` - Async callback invoked when connection state changes + /// + /// # Example + /// + /// ```dart + /// await client.onWebsocketStateChange( + /// onStateChange: (state) async { + /// print('Connection state: ${state.name}'); + /// }, + /// ); + /// ``` + Future onWebsocketStateChange({ + required FutureOr Function(WebSocketConnectionState) onStateChange, + }) => RustLib.instance.api.crateMobileConvexClientOnWebsocketStateChange( + that: this, + onStateChange: onStateChange, + ); + + /// Executes a query on the Convex backend. + Future query({ + required String name, + required Map args, + }) => RustLib.instance.api.crateMobileConvexClientQuery( + that: this, + name: name, + args: args, + ); + + /// Forces a WebSocket reconnect while retaining current client state. + Future reconnectNow({required String reason}) => RustLib.instance.api + .crateMobileConvexClientReconnectNow(that: this, reason: reason); + + /// Sets authentication token for the client. + Future setAuth({String? token}) => RustLib.instance.api + .crateMobileConvexClientSetAuth(that: this, token: token); + + /// Sets authentication with token refresh on every WebSocket reconnect. + /// + /// The callback is owned by the upstream Convex client so authentication + /// and query state are replayed together after a disconnect. + /// + /// Returns an AuthHandle that can be used to dispose the auth session. + Future setAuthWithRefresh({ + required FutureOr Function() fetchToken, + required FutureOr Function(bool) onAuthChange, + }) => RustLib.instance.api.crateMobileConvexClientSetAuthWithRefresh( + that: this, + fetchToken: fetchToken, + onAuthChange: onAuthChange, + ); + + /// Subscribes to real-time updates from a Convex query. + Future subscribe({ + required String name, + required Map args, + required FutureOr Function(String) onUpdate, + required FutureOr Function(String, String?) onError, + }) => RustLib.instance.api.crateMobileConvexClientSubscribe( + that: this, + name: name, + args: args, + onUpdate: onUpdate, + onError: onError, + ); +} + +@sealed +class SubscriptionHandleImpl extends RustOpaque implements SubscriptionHandle { + // Not to be used by end users + SubscriptionHandleImpl.frbInternalDcoDecode(List wire) + : super.frbInternalDcoDecode(wire, _kStaticData); + + // Not to be used by end users + SubscriptionHandleImpl.frbInternalSseDecode( + BigInt ptr, + int externalSizeOnNative, + ) : super.frbInternalSseDecode(ptr, externalSizeOnNative, _kStaticData); + + static final _kStaticData = RustArcStaticData( + rustArcIncrementStrongCount: + RustLib.instance.api.rust_arc_increment_strong_count_SubscriptionHandle, + rustArcDecrementStrongCount: + RustLib.instance.api.rust_arc_decrement_strong_count_SubscriptionHandle, + rustArcDecrementStrongCountPtr: RustLib + .instance + .api + .rust_arc_decrement_strong_count_SubscriptionHandlePtr, + ); + + /// Cancels the subscription by sending a cancellation signal. + void cancel() => + RustLib.instance.api.crateSubscriptionHandleCancel(that: this); +} diff --git a/third_party/convex_flutter/lib/src/rust/frb_generated.io.dart b/third_party/convex_flutter/lib/src/rust/frb_generated.io.dart new file mode 100644 index 00000000..7a8d9851 --- /dev/null +++ b/third_party/convex_flutter/lib/src/rust/frb_generated.io.dart @@ -0,0 +1,738 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +import 'dart:async'; +import 'dart:convert'; +import 'dart:ffi' as ffi; +import 'frb_generated.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_io.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_AuthHandlePtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandlePtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberDartFnPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFnPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_MobileConvexClientPtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClientPtr; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_SubscriptionHandlePtr => wire + ._rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandlePtr; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + AuthHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + AuthHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + FutureOr Function(String) + dco_decode_DartFn_Inputs_String_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(String, String?) + dco_decode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_opt_String_AnyhowException(dynamic raw); + + @protected + FutureOr Function(bool) + dco_decode_DartFn_Inputs_bool_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(WebSocketConnectionState) + dco_decode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + Object dco_decode_DartOpaque(dynamic raw); + + @protected + Map dco_decode_Map_String_String_None(dynamic raw); + + @protected + AuthHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + String dco_decode_String(dynamic raw); + + @protected + QuerySubscriber dco_decode_TraitDef_QuerySubscriber(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + ClientError dco_decode_client_error(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_isize(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + List<(String, String)> dco_decode_list_record_string_string(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + + @protected + (String, String) dco_decode_record_string_string(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + WebSocketConnectionState dco_decode_web_socket_connection_state(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + AuthHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + AuthHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer); + + @protected + Map sse_decode_Map_String_String_None( + SseDeserializer deserializer, + ); + + @protected + AuthHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + ClientError sse_decode_client_error(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + List<(String, String)> sse_decode_list_record_string_string( + SseDeserializer deserializer, + ); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + WebSocketConnectionState sse_decode_web_socket_connection_state( + SseDeserializer deserializer, + ); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_Output_unit_AnyhowException( + FutureOr Function(String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + FutureOr Function(String, String?) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs__Output_opt_String_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_bool_Output_unit_AnyhowException( + FutureOr Function(bool) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + FutureOr Function(WebSocketConnectionState) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer); + + @protected + void sse_encode_Map_String_String_None( + Map self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_client_error(ClientError self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_record_string_string( + List<(String, String)> self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_web_socket_connection_state( + WebSocketConnectionState self, + SseSerializer serializer, + ); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + factory RustLibWire.fromExternalLibrary(ExternalLibrary lib) => + RustLibWire(lib.ffiDynamicLibrary); + + /// Holds the symbol lookup function. + final ffi.Pointer Function(String symbolName) + _lookup; + + /// The symbols are looked up in [dynamicLibrary]. + RustLibWire(ffi.DynamicLibrary dynamicLibrary) + : _lookup = dynamicLibrary.lookup; + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandlePtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandlePtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandlePtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandlePtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFnPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFnPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFnPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFnPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClientPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClientPtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClientPtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClientPtr + .asFunction)>(); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ffi.Pointer ptr, + ) { + return _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr, + ); + } + + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandlePtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle', + ); + late final _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle = + _rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandlePtr + .asFunction)>(); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ffi.Pointer ptr, + ) { + return _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr, + ); + } + + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandlePtr = + _lookup)>>( + 'frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle', + ); + late final _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle = + _rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandlePtr + .asFunction)>(); +} diff --git a/third_party/convex_flutter/lib/src/rust/frb_generated.web.dart b/third_party/convex_flutter/lib/src/rust/frb_generated.web.dart new file mode 100644 index 00000000..0af0c816 --- /dev/null +++ b/third_party/convex_flutter/lib/src/rust/frb_generated.web.dart @@ -0,0 +1,698 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: unused_import, unused_element, unnecessary_import, duplicate_ignore, invalid_use_of_internal_member, annotate_overrides, non_constant_identifier_names, curly_braces_in_flow_control_structures, prefer_const_literals_to_create_immutables, unused_field + +// Static analysis wrongly picks the IO variant, thus ignore this +// ignore_for_file: argument_type_not_assignable + +import 'dart:async'; +import 'dart:convert'; +import 'frb_generated.dart'; +import 'lib.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated_web.dart'; + +abstract class RustLibApiImplPlatform extends BaseApiImpl { + RustLibApiImplPlatform({ + required super.handler, + required super.wire, + required super.generalizedFrbRustBinding, + required super.portManager, + }); + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_AuthHandlePtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_CallbackSubscriberDartFnPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_MobileConvexClientPtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient; + + CrossPlatformFinalizerArg + get rust_arc_decrement_strong_count_SubscriptionHandlePtr => wire + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle; + + @protected + AnyhowException dco_decode_AnyhowException(dynamic raw); + + @protected + AuthHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + AuthHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + FutureOr Function(String) + dco_decode_DartFn_Inputs_String_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(String, String?) + dco_decode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + FutureOr Function() + dco_decode_DartFn_Inputs__Output_opt_String_AnyhowException(dynamic raw); + + @protected + FutureOr Function(bool) + dco_decode_DartFn_Inputs_bool_Output_unit_AnyhowException(dynamic raw); + + @protected + FutureOr Function(WebSocketConnectionState) + dco_decode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + dynamic raw, + ); + + @protected + Object dco_decode_DartOpaque(dynamic raw); + + @protected + Map dco_decode_Map_String_String_None(dynamic raw); + + @protected + AuthHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + dynamic raw, + ); + + @protected + CallbackSubscriber + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + dynamic raw, + ); + + @protected + CallbackSubscriberDartFn + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + dynamic raw, + ); + + @protected + MobileConvexClient + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + dynamic raw, + ); + + @protected + SubscriptionHandle + dco_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + dynamic raw, + ); + + @protected + String dco_decode_String(dynamic raw); + + @protected + QuerySubscriber dco_decode_TraitDef_QuerySubscriber(dynamic raw); + + @protected + bool dco_decode_bool(dynamic raw); + + @protected + ClientError dco_decode_client_error(dynamic raw); + + @protected + int dco_decode_i_32(dynamic raw); + + @protected + PlatformInt64 dco_decode_isize(dynamic raw); + + @protected + Uint8List dco_decode_list_prim_u_8_strict(dynamic raw); + + @protected + List<(String, String)> dco_decode_list_record_string_string(dynamic raw); + + @protected + String? dco_decode_opt_String(dynamic raw); + + @protected + (String, String) dco_decode_record_string_string(dynamic raw); + + @protected + int dco_decode_u_8(dynamic raw); + + @protected + void dco_decode_unit(dynamic raw); + + @protected + BigInt dco_decode_usize(dynamic raw); + + @protected + WebSocketConnectionState dco_decode_web_socket_connection_state(dynamic raw); + + @protected + AnyhowException sse_decode_AnyhowException(SseDeserializer deserializer); + + @protected + AuthHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + AuthHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + Object sse_decode_DartOpaque(SseDeserializer deserializer); + + @protected + Map sse_decode_Map_String_String_None( + SseDeserializer deserializer, + ); + + @protected + AuthHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriber + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + SseDeserializer deserializer, + ); + + @protected + CallbackSubscriberDartFn + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + SseDeserializer deserializer, + ); + + @protected + MobileConvexClient + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + SseDeserializer deserializer, + ); + + @protected + SubscriptionHandle + sse_decode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SseDeserializer deserializer, + ); + + @protected + String sse_decode_String(SseDeserializer deserializer); + + @protected + bool sse_decode_bool(SseDeserializer deserializer); + + @protected + ClientError sse_decode_client_error(SseDeserializer deserializer); + + @protected + int sse_decode_i_32(SseDeserializer deserializer); + + @protected + PlatformInt64 sse_decode_isize(SseDeserializer deserializer); + + @protected + Uint8List sse_decode_list_prim_u_8_strict(SseDeserializer deserializer); + + @protected + List<(String, String)> sse_decode_list_record_string_string( + SseDeserializer deserializer, + ); + + @protected + String? sse_decode_opt_String(SseDeserializer deserializer); + + @protected + (String, String) sse_decode_record_string_string( + SseDeserializer deserializer, + ); + + @protected + int sse_decode_u_8(SseDeserializer deserializer); + + @protected + void sse_decode_unit(SseDeserializer deserializer); + + @protected + BigInt sse_decode_usize(SseDeserializer deserializer); + + @protected + WebSocketConnectionState sse_decode_web_socket_connection_state( + SseDeserializer deserializer, + ); + + @protected + void sse_encode_AnyhowException( + AnyhowException self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Owned_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_Auto_Ref_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_Output_unit_AnyhowException( + FutureOr Function(String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + FutureOr Function(String, String?) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs__Output_opt_String_AnyhowException( + FutureOr Function() self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartFn_Inputs_bool_Output_unit_AnyhowException( + FutureOr Function(bool) self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + FutureOr Function(WebSocketConnectionState) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_DartOpaque(Object self, SseSerializer serializer); + + @protected + void sse_encode_Map_String_String_None( + Map self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + AuthHandle self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + CallbackSubscriber self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + CallbackSubscriberDartFn self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + MobileConvexClient self, + SseSerializer serializer, + ); + + @protected + void + sse_encode_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + SubscriptionHandle self, + SseSerializer serializer, + ); + + @protected + void sse_encode_String(String self, SseSerializer serializer); + + @protected + void sse_encode_bool(bool self, SseSerializer serializer); + + @protected + void sse_encode_client_error(ClientError self, SseSerializer serializer); + + @protected + void sse_encode_i_32(int self, SseSerializer serializer); + + @protected + void sse_encode_isize(PlatformInt64 self, SseSerializer serializer); + + @protected + void sse_encode_list_prim_u_8_strict( + Uint8List self, + SseSerializer serializer, + ); + + @protected + void sse_encode_list_record_string_string( + List<(String, String)> self, + SseSerializer serializer, + ); + + @protected + void sse_encode_opt_String(String? self, SseSerializer serializer); + + @protected + void sse_encode_record_string_string( + (String, String) self, + SseSerializer serializer, + ); + + @protected + void sse_encode_u_8(int self, SseSerializer serializer); + + @protected + void sse_encode_unit(void self, SseSerializer serializer); + + @protected + void sse_encode_usize(BigInt self, SseSerializer serializer); + + @protected + void sse_encode_web_socket_connection_state( + WebSocketConnectionState self, + SseSerializer serializer, + ); +} + +// Section: wire_class + +class RustLibWire implements BaseWire { + RustLibWire.fromExternalLibrary(ExternalLibrary lib); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr, + ); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr, + ); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr, + ); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr, + ); + + void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + int ptr, + ) => wasmModule + .rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr, + ); + + void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + int ptr, + ) => wasmModule + .rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr, + ); +} + +@JS('wasm_bindgen') +external RustLibWasmModule get wasmModule; + +@JS() +@anonymous +extension type RustLibWasmModule._(JSObject _) implements JSObject { + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + int ptr, + ); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + int ptr, + ); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + int ptr, + ); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + int ptr, + ); + + external void + rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + int ptr, + ); + + external void + rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + int ptr, + ); +} diff --git a/third_party/convex_flutter/lib/src/rust/lib.dart b/third_party/convex_flutter/lib/src/rust/lib.dart new file mode 100644 index 00000000..a6e06b11 --- /dev/null +++ b/third_party/convex_flutter/lib/src/rust/lib.dart @@ -0,0 +1,161 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +// ignore_for_file: invalid_use_of_internal_member, unused_import, unnecessary_import + +import 'frb_generated.dart'; +import 'package:flutter_rust_bridge/flutter_rust_bridge_for_generated.dart'; +import 'package:freezed_annotation/freezed_annotation.dart' hide protected; +part 'lib.freezed.dart'; + +// These functions are ignored because they are not marked as `pub`: `connected_client`, `handle_direct_function_result`, `internal_action`, `internal_mutation`, `internal_set_auth`, `internal_subscribe`, `new`, `new`, `parse_json_args` +// These function are ignored because they are on traits that is not defined in current crate (put an empty `#[frb]` on it to unignore): `clone`, `fmt`, `fmt`, `fmt`, `from`, `from` + +// Rust type: RustOpaqueMoi> +abstract class AuthHandle implements RustOpaqueInterface { + /// Disposes the auth session, stopping the token refresh loop and clearing authentication. + @override + void dispose(); + + /// Returns whether the user is currently authenticated. + bool isAuthenticated(); +} + +// Rust type: RustOpaqueMoi> +abstract class CallbackSubscriber + implements RustOpaqueInterface, QuerySubscriber { + @override + Future onError({required String message, String? value}); + + @override + Future onUpdate({required String value}); +} + +// Rust type: RustOpaqueMoi> +abstract class CallbackSubscriberDartFn + implements RustOpaqueInterface, QuerySubscriber { + @override + Future onError({required String message, String? value}); + + @override + Future onUpdate({required String value}); +} + +// Rust type: RustOpaqueMoi> +abstract class MobileConvexClient implements RustOpaqueInterface { + /// Executes an action on the Convex backend. + Future action({ + required String name, + required Map args, + }); + + /// Executes a mutation on the Convex backend. + Future mutation({ + required String name, + required Map args, + }); + + /// Creates a new MobileConvexClient instance with the given deployment URL and client ID. + factory MobileConvexClient({ + required String deploymentUrl, + required String clientId, + }) => RustLib.instance.api.crateMobileConvexClientNew( + deploymentUrl: deploymentUrl, + clientId: clientId, + ); + + /// Sets up WebSocket connection state change listener. + /// + /// Must be called BEFORE any queries/mutations to capture all state changes. + /// The callback will be invoked whenever the WebSocket transitions between + /// Connected and Connecting states. + /// + /// # Arguments + /// + /// * `on_state_change` - Async callback invoked when connection state changes + /// + /// # Example + /// + /// ```dart + /// await client.onWebsocketStateChange( + /// onStateChange: (state) async { + /// print('Connection state: ${state.name}'); + /// }, + /// ); + /// ``` + Future onWebsocketStateChange({ + required FutureOr Function(WebSocketConnectionState) onStateChange, + }); + + /// Executes a query on the Convex backend. + Future query({ + required String name, + required Map args, + }); + + /// Forces a WebSocket reconnect while retaining current client state. + Future reconnectNow({required String reason}); + + /// Sets authentication token for the client. + Future setAuth({String? token}); + + /// Sets authentication with token refresh on every WebSocket reconnect. + /// + /// The callback is owned by the upstream Convex client so authentication + /// and query state are replayed together after a disconnect. + /// + /// Returns an AuthHandle that can be used to dispose the auth session. + Future setAuthWithRefresh({ + required FutureOr Function() fetchToken, + required FutureOr Function(bool) onAuthChange, + }); + + /// Subscribes to real-time updates from a Convex query. + Future subscribe({ + required String name, + required Map args, + required FutureOr Function(String) onUpdate, + required FutureOr Function(String, String?) onError, + }); +} + +// Rust type: RustOpaqueMoi> +abstract class SubscriptionHandle implements RustOpaqueInterface { + /// Cancels the subscription by sending a cancellation signal. + void cancel(); +} + +abstract class QuerySubscriber { + Future onError({required String message, String? value}); + + Future onUpdate({required String value}); +} + +@freezed +sealed class ClientError with _$ClientError implements FrbException { + const ClientError._(); + + /// An internal error within the mobile Convex client. + const factory ClientError.internalError({required String msg}) = + ClientError_InternalError; + + /// An application-specific error from a remote Convex backend function. + const factory ClientError.convexError({required String data}) = + ClientError_ConvexError; + + /// An unexpected server-side error from a remote Convex function. + const factory ClientError.serverError({required String msg}) = + ClientError_ServerError; +} + +/// WebSocket connection state exposed to Flutter/Dart. +/// +/// This enum represents the current state of the WebSocket connection +/// to the Convex backend, allowing real-time connection monitoring. +enum WebSocketConnectionState { + /// The WebSocket is open and connected to the Convex backend. + connected, + + /// The WebSocket is closed and is connecting or reconnecting. + connecting, +} diff --git a/third_party/convex_flutter/lib/src/rust/lib.freezed.dart b/third_party/convex_flutter/lib/src/rust/lib.freezed.dart new file mode 100644 index 00000000..277a15fc --- /dev/null +++ b/third_party/convex_flutter/lib/src/rust/lib.freezed.dart @@ -0,0 +1,378 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// coverage:ignore-file +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'lib.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +// dart format off +T _$identity(T value) => value; +/// @nodoc +mixin _$ClientError { + + + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ClientError); +} + + +@override +int get hashCode => runtimeType.hashCode; + +@override +String toString() { + return 'ClientError()'; +} + + +} + +/// @nodoc +class $ClientErrorCopyWith<$Res> { +$ClientErrorCopyWith(ClientError _, $Res Function(ClientError) __); +} + + +/// Adds pattern-matching-related methods to [ClientError]. +extension ClientErrorPatterns on ClientError { +/// A variant of `map` that fallback to returning `orElse`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeMap({TResult Function( ClientError_InternalError value)? internalError,TResult Function( ClientError_ConvexError value)? convexError,TResult Function( ClientError_ServerError value)? serverError,required TResult orElse(),}){ +final _that = this; +switch (_that) { +case ClientError_InternalError() when internalError != null: +return internalError(_that);case ClientError_ConvexError() when convexError != null: +return convexError(_that);case ClientError_ServerError() when serverError != null: +return serverError(_that);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// Callbacks receives the raw object, upcasted. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case final Subclass2 value: +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult map({required TResult Function( ClientError_InternalError value) internalError,required TResult Function( ClientError_ConvexError value) convexError,required TResult Function( ClientError_ServerError value) serverError,}){ +final _that = this; +switch (_that) { +case ClientError_InternalError(): +return internalError(_that);case ClientError_ConvexError(): +return convexError(_that);case ClientError_ServerError(): +return serverError(_that);} +} +/// A variant of `map` that fallback to returning `null`. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case final Subclass value: +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? mapOrNull({TResult? Function( ClientError_InternalError value)? internalError,TResult? Function( ClientError_ConvexError value)? convexError,TResult? Function( ClientError_ServerError value)? serverError,}){ +final _that = this; +switch (_that) { +case ClientError_InternalError() when internalError != null: +return internalError(_that);case ClientError_ConvexError() when convexError != null: +return convexError(_that);case ClientError_ServerError() when serverError != null: +return serverError(_that);case _: + return null; + +} +} +/// A variant of `when` that fallback to an `orElse` callback. +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return orElse(); +/// } +/// ``` + +@optionalTypeArgs TResult maybeWhen({TResult Function( String msg)? internalError,TResult Function( String data)? convexError,TResult Function( String msg)? serverError,required TResult orElse(),}) {final _that = this; +switch (_that) { +case ClientError_InternalError() when internalError != null: +return internalError(_that.msg);case ClientError_ConvexError() when convexError != null: +return convexError(_that.data);case ClientError_ServerError() when serverError != null: +return serverError(_that.msg);case _: + return orElse(); + +} +} +/// A `switch`-like method, using callbacks. +/// +/// As opposed to `map`, this offers destructuring. +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case Subclass2(:final field2): +/// return ...; +/// } +/// ``` + +@optionalTypeArgs TResult when({required TResult Function( String msg) internalError,required TResult Function( String data) convexError,required TResult Function( String msg) serverError,}) {final _that = this; +switch (_that) { +case ClientError_InternalError(): +return internalError(_that.msg);case ClientError_ConvexError(): +return convexError(_that.data);case ClientError_ServerError(): +return serverError(_that.msg);} +} +/// A variant of `when` that fallback to returning `null` +/// +/// It is equivalent to doing: +/// ```dart +/// switch (sealedClass) { +/// case Subclass(:final field): +/// return ...; +/// case _: +/// return null; +/// } +/// ``` + +@optionalTypeArgs TResult? whenOrNull({TResult? Function( String msg)? internalError,TResult? Function( String data)? convexError,TResult? Function( String msg)? serverError,}) {final _that = this; +switch (_that) { +case ClientError_InternalError() when internalError != null: +return internalError(_that.msg);case ClientError_ConvexError() when convexError != null: +return convexError(_that.data);case ClientError_ServerError() when serverError != null: +return serverError(_that.msg);case _: + return null; + +} +} + +} + +/// @nodoc + + +class ClientError_InternalError extends ClientError { + const ClientError_InternalError({required this.msg}): super._(); + + + final String msg; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ClientError_InternalErrorCopyWith get copyWith => _$ClientError_InternalErrorCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ClientError_InternalError&&(identical(other.msg, msg) || other.msg == msg)); +} + + +@override +int get hashCode => Object.hash(runtimeType,msg); + +@override +String toString() { + return 'ClientError.internalError(msg: $msg)'; +} + + +} + +/// @nodoc +abstract mixin class $ClientError_InternalErrorCopyWith<$Res> implements $ClientErrorCopyWith<$Res> { + factory $ClientError_InternalErrorCopyWith(ClientError_InternalError value, $Res Function(ClientError_InternalError) _then) = _$ClientError_InternalErrorCopyWithImpl; +@useResult +$Res call({ + String msg +}); + + + + +} +/// @nodoc +class _$ClientError_InternalErrorCopyWithImpl<$Res> + implements $ClientError_InternalErrorCopyWith<$Res> { + _$ClientError_InternalErrorCopyWithImpl(this._self, this._then); + + final ClientError_InternalError _self; + final $Res Function(ClientError_InternalError) _then; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? msg = null,}) { + return _then(ClientError_InternalError( +msg: null == msg ? _self.msg : msg // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class ClientError_ConvexError extends ClientError { + const ClientError_ConvexError({required this.data}): super._(); + + + final String data; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ClientError_ConvexErrorCopyWith get copyWith => _$ClientError_ConvexErrorCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ClientError_ConvexError&&(identical(other.data, data) || other.data == data)); +} + + +@override +int get hashCode => Object.hash(runtimeType,data); + +@override +String toString() { + return 'ClientError.convexError(data: $data)'; +} + + +} + +/// @nodoc +abstract mixin class $ClientError_ConvexErrorCopyWith<$Res> implements $ClientErrorCopyWith<$Res> { + factory $ClientError_ConvexErrorCopyWith(ClientError_ConvexError value, $Res Function(ClientError_ConvexError) _then) = _$ClientError_ConvexErrorCopyWithImpl; +@useResult +$Res call({ + String data +}); + + + + +} +/// @nodoc +class _$ClientError_ConvexErrorCopyWithImpl<$Res> + implements $ClientError_ConvexErrorCopyWith<$Res> { + _$ClientError_ConvexErrorCopyWithImpl(this._self, this._then); + + final ClientError_ConvexError _self; + final $Res Function(ClientError_ConvexError) _then; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? data = null,}) { + return _then(ClientError_ConvexError( +data: null == data ? _self.data : data // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +/// @nodoc + + +class ClientError_ServerError extends ClientError { + const ClientError_ServerError({required this.msg}): super._(); + + + final String msg; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@JsonKey(includeFromJson: false, includeToJson: false) +@pragma('vm:prefer-inline') +$ClientError_ServerErrorCopyWith get copyWith => _$ClientError_ServerErrorCopyWithImpl(this, _$identity); + + + +@override +bool operator ==(Object other) { + return identical(this, other) || (other.runtimeType == runtimeType&&other is ClientError_ServerError&&(identical(other.msg, msg) || other.msg == msg)); +} + + +@override +int get hashCode => Object.hash(runtimeType,msg); + +@override +String toString() { + return 'ClientError.serverError(msg: $msg)'; +} + + +} + +/// @nodoc +abstract mixin class $ClientError_ServerErrorCopyWith<$Res> implements $ClientErrorCopyWith<$Res> { + factory $ClientError_ServerErrorCopyWith(ClientError_ServerError value, $Res Function(ClientError_ServerError) _then) = _$ClientError_ServerErrorCopyWithImpl; +@useResult +$Res call({ + String msg +}); + + + + +} +/// @nodoc +class _$ClientError_ServerErrorCopyWithImpl<$Res> + implements $ClientError_ServerErrorCopyWith<$Res> { + _$ClientError_ServerErrorCopyWithImpl(this._self, this._then); + + final ClientError_ServerError _self; + final $Res Function(ClientError_ServerError) _then; + +/// Create a copy of ClientError +/// with the given fields replaced by the non-null parameter values. +@pragma('vm:prefer-inline') $Res call({Object? msg = null,}) { + return _then(ClientError_ServerError( +msg: null == msg ? _self.msg : msg // ignore: cast_nullable_to_non_nullable +as String, + )); +} + + +} + +// dart format on diff --git a/third_party/convex_flutter/lib/src/utils.dart b/third_party/convex_flutter/lib/src/utils.dart new file mode 100644 index 00000000..f1dd0ae0 --- /dev/null +++ b/third_party/convex_flutter/lib/src/utils.dart @@ -0,0 +1,5 @@ +import 'dart:convert'; + +Map buildArgs(Map record) { + return {for (var entry in record.entries) entry.key: jsonEncode(entry.value)}; +} diff --git a/third_party/convex_flutter/linux/CMakeLists.txt b/third_party/convex_flutter/linux/CMakeLists.txt new file mode 100644 index 00000000..11cd02c1 --- /dev/null +++ b/third_party/convex_flutter/linux/CMakeLists.txt @@ -0,0 +1,19 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "convex_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../rust convex_flutter "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(convex_flutter_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/third_party/convex_flutter/macos/Classes/dummy_file.c b/third_party/convex_flutter/macos/Classes/dummy_file.c new file mode 100644 index 00000000..e06dab99 --- /dev/null +++ b/third_party/convex_flutter/macos/Classes/dummy_file.c @@ -0,0 +1 @@ +// This is an empty file to force CocoaPods to create a framework. diff --git a/third_party/convex_flutter/macos/convex_flutter.podspec b/third_party/convex_flutter/macos/convex_flutter.podspec new file mode 100644 index 00000000..15b4329c --- /dev/null +++ b/third_party/convex_flutter/macos/convex_flutter.podspec @@ -0,0 +1,44 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint convex_flutter.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'convex_flutter' + s.version = '0.0.1' + s.summary = 'A new Flutter FFI plugin project.' + s.description = <<-DESC +A new Flutter FFI plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + # This will ensure the source files in Classes/ are included in the native + # builds of apps using this FFI plugin. Podspec does not support relative + # paths, so Classes contains a forwarder C file that relatively imports + # `../src/*` so that the C sources can be shared among all target platforms. + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' + + s.script_phase = { + :name => 'Build Rust library', + # First argument is relative path to the `rust` folder, second is name of rust library + :script => 'sh "$PODS_TARGET_SRCROOT/../cargokit/build_pod.sh" ../rust convex_flutter', + :execution_position => :before_compile, + :input_files => ['${BUILT_PRODUCTS_DIR}/cargokit_phony'], + # Let XCode know that the static library referenced in -force_load below is + # created by this build step. + :output_files => ["${BUILT_PRODUCTS_DIR}/libconvex_flutter.a"], + } + s.pod_target_xcconfig = { + 'DEFINES_MODULE' => 'YES', + # Flutter.framework does not contain a i386 slice. + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + 'OTHER_LDFLAGS' => '-force_load ${BUILT_PRODUCTS_DIR}/libconvex_flutter.a', + } +end diff --git a/third_party/convex_flutter/pubspec.yaml b/third_party/convex_flutter/pubspec.yaml new file mode 100644 index 00000000..5505d5bd --- /dev/null +++ b/third_party/convex_flutter/pubspec.yaml @@ -0,0 +1,95 @@ +name: convex_flutter +description: Multi-platform Convex backend integration for Flutter. Real-time WebSocket, subscriptions, auth, lifecycle management. Supports web (pure Dart) and native. +version: 3.0.1 +repository: https://github.com/jkuldev/convex_flutter +homepage: https://jkuldev.com + +environment: + sdk: ^3.8.1 + flutter: '>=3.3.0' + +dependencies: + flutter: + sdk: flutter + flutter_rust_bridge: 2.11.1 + flutter_web_plugins: + sdk: flutter + freezed_annotation: ^3.1.0 + plugin_platform_interface: ^2.0.2 + http: ^1.2.0 # HTTP client for web REST fallback + web: ^1.0.0 # WebSocket API for web platform + +dev_dependencies: + ffi: ^2.1.3 + ffigen: ^13.0.0 + flutter_test: + sdk: flutter + flutter_lints: ^5.0.0 + integration_test: + sdk: flutter + freezed: ^3.1.0 + build_runner: ^2.5.4 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + # + # Please refer to README.md for a detailed explanation. + plugin: + platforms: + android: + ffiPlugin: true + ios: + ffiPlugin: true + linux: + ffiPlugin: true + macos: + ffiPlugin: true + windows: + ffiPlugin: true + web: + pluginClass: ConvexFlutterWeb + fileName: convex_flutter_web.dart + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/third_party/convex_flutter/rust/Cargo.lock b/third_party/convex_flutter/rust/Cargo.lock new file mode 100644 index 00000000..a8ba7533 --- /dev/null +++ b/third_party/convex_flutter/rust/Cargo.lock @@ -0,0 +1,2187 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allo-isolate" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449e356a4864c017286dbbec0e12767ea07efba29e3b7d984194c2a7ff3c4550" +dependencies = [ + "anyhow", + "atomic", + "backtrace", +] + +[[package]] +name = "android_log-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84521a3cf562bc62942e294181d9eef17eb38ceb8c68677bc49f144e4c3d4f8d" + +[[package]] +name = "android_logger" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b07e8e73d720a1f2e4b6014766e6039fd2e96a4fa44e2a78d0e1fa2ff49826" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "android_logger" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb4e440d04be07da1f1bf44fb4495ebd58669372fe0cffa6e48595ac5bd88a3" +dependencies = [ + "android_log-sys", + "env_filter", + "log", +] + +[[package]] +name = "anyhow" +version = "1.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" + +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" + +[[package]] +name = "async-once-cell" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "windows-link", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitmaps" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d084b0137aaa901caf9f1e8b21daa6aa24d41cd806e111335541eff9683bd6" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "build-target" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "832133bbabbbaa9fbdba793456a2827627a7d2b8fb96032fa1e7666d7895832b" + +[[package]] +name = "bumpalo" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" + +[[package]] +name = "bytemuck" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbdf580320f38b612e485521afda1ee26d10cc9884efaaa750d383e13e3c5f4" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convex" +version = "0.10.4" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.13.1", + "bytes", + "convex_sync_types", + "futures", + "imbl", + "rand", + "serde_json", + "thiserror 2.0.17", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "convex_flutter" +version = "0.1.0" +dependencies = [ + "android_logger 0.14.1", + "anyhow", + "async-once-cell", + "convex", + "flutter_rust_bridge", + "futures", + "log", + "maplit", + "once_cell", + "parking_lot", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-stream", +] + +[[package]] +name = "convex_sync_types" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba8235188b091cc50205a436bf6505cb386be208263fecdb4b62bd2b3c90a0a" +dependencies = [ + "anyhow", + "base64 0.13.1", + "bytes", + "derive_more", + "headers", + "rand", + "serde", + "serde_json", + "strum", + "uuid", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "dart-sys" +version = "4.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57967e4b200d767d091b961d6ab42cc7d0cc14fe9e052e75d0d3cf9eb732d895" +dependencies = [ + "cc", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "data-encoding" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" + +[[package]] +name = "delegate-attr" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51aac4c99b2e6775164b412ea33ae8441b2fde2dbf05a20bc0052a63d08c475b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "env_filter" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf3c259d255ca70051b30e2e95b5446cdb8949ac4cd22c0d7fd634d89f568e2" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" + +[[package]] +name = "flutter_rust_bridge" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde126295b2acc5f0a712e265e91b6fdc0ed38767496483e592ae7134db83725" +dependencies = [ + "allo-isolate", + "android_logger 0.15.1", + "anyhow", + "build-target", + "bytemuck", + "byteorder", + "console_error_panic_hook", + "dart-sys", + "delegate-attr", + "flutter_rust_bridge_macros", + "futures", + "js-sys", + "lazy_static", + "log", + "oslog", + "portable-atomic", + "threadpool", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "flutter_rust_bridge_macros" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f0420326b13675321b194928bb7830043b68cf8b810e1c651285c747abb080" +dependencies = [ + "hex", + "md-5", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "headers" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3314d5adb5d94bcdf56771f2e50dbbc80bb4bdf88967526706205ac9eff24eb" +dependencies = [ + "base64 0.22.1", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "imbl" +version = "7.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ea8d4c37ee560727e824d62804183624d371e632019b0e9e3532bce64a33e5" +dependencies = [ + "archery", + "bitmaps", + "equivalent", + "imbl-sized-chunks", + "rand_core", + "rand_xoshiro", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", +] + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "js-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "linux-raw-sys" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-src" +version = "300.5.4+3.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507b3792995dae9b0df8a1c1e3771e8418b7c2d9f0baeba32e6fe8b06c7cb72" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "oslog" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d2043d1f61d77cb2f4b1f7b7b2295f40507f5f8e9d1c8bf10a1ca5f97a3969" +dependencies = [ + "cc", + "dashmap", + "log", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "portable-atomic" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f89776e4d69bb58bc6993e99ffa1d11f228b839984854c7daeb5d37f87cbe950" + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro2" +version = "1.0.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535d180e0ecab6268a3e718bb9fd44db66bbbc256257165fc699dadf70d16fe7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc74d9a594b72ae6656596548f56f667211f8a97b3d4c3d467150794690dc40a" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f7d92ca342cea22a06f2121d944b4fd82af56988c270852495420f961d4ace" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d31c77bdf42a745371d260a26ca7163f1e0924b64afa0b688e61b5a9fa02f16" +dependencies = [ + "fastrand", + "getrandom 0.3.4", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl 2.0.17", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror 2.0.17", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e054861b4bd027cd373e18e8d8d8e6548085000e41290d95ce0c373a654b4a" +dependencies = [ + "getrandom 0.3.4", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836d9622d604feee9e5de25ac10e3ea5f2d65b41eac0d9ce72eb5deae707ce7c" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.5", +] + +[[package]] +name = "webpki-roots" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12bed680863276c63889429bfd6cab3b99943659923822de1c8a39c49e4d722c" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "668f5168d10b9ee831de31933dc111a459c97ec93225beb307aed970d1372dfd" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c7962b26b0a8685668b671ee4b54d007a67d4eaf05fda79ac0ecf41e32270f1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zmij" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc5a66a20078bf1251bde995aa2fdcc4b800c70b5d92dd2c62abc5c60f679f8" diff --git a/third_party/convex_flutter/rust/Cargo.toml b/third_party/convex_flutter/rust/Cargo.toml new file mode 100644 index 00000000..ce117698 --- /dev/null +++ b/third_party/convex_flutter/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "convex_flutter" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "staticlib"] + +[dependencies] +flutter_rust_bridge = "=2.11.1" +tokio = { version = "1", features = ["full"] } +android_logger = { version = "0.14.1" } +log = { version = "0.4.21" } +convex = { path = "../../convex_rs", features = ["rustls-tls-webpki-roots"] } +anyhow = { version = "1.0.86" } +thiserror = { version = "1.0.61" } +tokio-stream = { features = [ "io-util", "sync" ], version = "0.1" } +once_cell = { version = "1.19.0" } +futures = { version = "0.3" } +parking_lot = { version = "0.12.3" } +async-once-cell = { version = "0.5.3" } +serde_json = { version = "1.0.120" } +[lints.rust] +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(frb_expand)'] } + +[dev-dependencies] +maplit = { version = "1" } diff --git a/third_party/convex_flutter/rust/example/lib/main.dart b/third_party/convex_flutter/rust/example/lib/main.dart new file mode 100644 index 00000000..58dd592f --- /dev/null +++ b/third_party/convex_flutter/rust/example/lib/main.dart @@ -0,0 +1,195 @@ +import 'package:flutter/material.dart'; +import 'package:convex_flutter/convex_flutter.dart'; +import 'widgets/connection_status_indicator.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + + await ConvexClient.initialize( + ConvexConfig( + deploymentUrl: "https://your-deployment.convex.cloud", + clientId: "flutter-example-app", + operationTimeout: const Duration(seconds: 30), + ), + ); + + runApp(const ConvexExampleApp()); +} + +class ConvexExampleApp extends StatelessWidget { + const ConvexExampleApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Convex Flutter - WebSocket State Demo', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), + useMaterial3: true, + ), + home: const ConnectionDemoScreen(), + ); + } +} + +class ConnectionDemoScreen extends StatefulWidget { + const ConnectionDemoScreen({super.key}); + + @override + State createState() => _ConnectionDemoScreenState(); +} + +class _ConnectionDemoScreenState extends State { + final List _stateHistory = []; + + @override + void initState() { + super.initState(); + _listenToConnectionChanges(); + } + + void _listenToConnectionChanges() { + ConvexClient.instance.connectionState.listen((state) { + setState(() { + _stateHistory.insert(0, ConnectionEvent( + state: state, + timestamp: DateTime.now(), + )); + if (_stateHistory.length > 20) { + _stateHistory.removeLast(); + } + }); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('WebSocket State Demo'), + actions: const [ConnectionStatusIndicator()], + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildCurrentStateCard(), + const SizedBox(height: 16), + _buildFeatureCard(), + const SizedBox(height: 16), + _buildHistoryCard(), + ], + ), + ), + ); + } + + Widget _buildCurrentStateCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: StreamBuilder( + stream: ConvexClient.instance.connectionState, + initialData: ConvexClient.instance.currentConnectionState, + builder: (context, snapshot) { + final state = snapshot.data!; + final isConnected = state == WebSocketConnectionState.connected; + return Column( + children: [ + Icon( + isConnected ? Icons.cloud_done : Icons.cloud_sync, + color: isConnected ? Colors.green : Colors.orange, + size: 64, + ), + const SizedBox(height: 12), + Text( + state.name.toUpperCase(), + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: isConnected ? Colors.green : Colors.orange, + ), + ), + Text('isConnected: ${ConvexClient.instance.isConnected}'), + ], + ); + }, + ), + ), + ); + } + + Widget _buildFeatureCard() { + return Card( + color: Colors.blue.shade50, + child: const Padding( + padding: EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('✨ Real-time Connection State', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + SizedBox(height: 8), + Text('• Automatic state updates\n' + '• Two states: Connected/Connecting\n' + '• Access via connectionState stream'), + ], + ), + ), + ); + } + + Widget _buildHistoryCard() { + return Card( + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('History', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), + TextButton( + onPressed: () => setState(() => _stateHistory.clear()), + child: const Text('Clear'), + ), + ], + ), + if (_stateHistory.isEmpty) + const Padding( + padding: EdgeInsets.all(16), + child: Text('No state changes yet'), + ) + else + ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: _stateHistory.length, + itemBuilder: (context, index) { + final event = _stateHistory[index]; + final isConnected = + event.state == WebSocketConnectionState.connected; + return ListTile( + leading: Icon( + isConnected ? Icons.cloud_done : Icons.cloud_sync, + color: isConnected ? Colors.green : Colors.orange, + ), + title: Text(event.state.name.toUpperCase()), + subtitle: Text(event.timestamp.toString()), + ); + }, + ), + ], + ), + ), + ); + } +} + +class ConnectionEvent { + final WebSocketConnectionState state; + final DateTime timestamp; + ConnectionEvent({required this.state, required this.timestamp}); +} diff --git a/third_party/convex_flutter/rust/src/frb_generated.rs b/third_party/convex_flutter/rust/src/frb_generated.rs new file mode 100644 index 00000000..69cf500f --- /dev/null +++ b/third_party/convex_flutter/rust/src/frb_generated.rs @@ -0,0 +1,2013 @@ +// This file is automatically generated, so please do not edit it. +// @generated by `flutter_rust_bridge`@ 2.11.1. + +#![allow( + non_camel_case_types, + unused, + non_snake_case, + clippy::needless_return, + clippy::redundant_closure_call, + clippy::redundant_closure, + clippy::useless_conversion, + clippy::unit_arg, + clippy::unused_unit, + clippy::double_parens, + clippy::let_and_return, + clippy::too_many_arguments, + clippy::match_single_binding, + clippy::clone_on_copy, + clippy::let_unit_value, + clippy::deref_addrof, + clippy::explicit_auto_deref, + clippy::borrow_deref_ref, + clippy::needless_borrow +)] + +// Section: imports + +use crate::QuerySubscriber; +use crate::*; +use flutter_rust_bridge::for_generated::byteorder::{NativeEndian, ReadBytesExt, WriteBytesExt}; +use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; +use flutter_rust_bridge::{Handler, IntoIntoDart}; + +// Section: boilerplate + +flutter_rust_bridge::frb_generated_boilerplate!( + default_stream_sink_codec = SseCodec, + default_rust_opaque = RustOpaqueMoi, + default_rust_auto_opaque = RustAutoOpaqueMoi, +); +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_VERSION: &str = "2.11.1"; +pub(crate) const FLUTTER_RUST_BRIDGE_CODEGEN_CONTENT_HASH: i32 = -829523767; + +// Section: executor + +flutter_rust_bridge::frb_generated_default_handler!(); + +// Section: wire_funcs + +fn wire__crate__AuthHandle_dispose_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "AuthHandle_dispose", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::AuthHandle::dispose(&*api_that_guard); + })?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__AuthHandle_is_authenticated_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "AuthHandle_is_authenticated", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + Result::<_, ()>::Ok(crate::AuthHandle::is_authenticated(&*api_that_guard))?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__CallbackSubscriberDartFn_on_error_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "CallbackSubscriberDartFn_on_error", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_message = ::sse_decode(&mut deserializer); + let api_value = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::CallbackSubscriberDartFn::on_error( + &*api_that_guard, + api_message, + api_value, + ); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__CallbackSubscriberDartFn_on_update_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "CallbackSubscriberDartFn_on_update", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_value = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::CallbackSubscriberDartFn::on_update(&*api_that_guard, api_value); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__CallbackSubscriber_on_error_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "CallbackSubscriber_on_error", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_message = ::sse_decode(&mut deserializer); + let api_value = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::CallbackSubscriber::on_error( + &*api_that_guard, + api_message, + api_value, + ); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__CallbackSubscriber_on_update_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_normal::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "CallbackSubscriber_on_update", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_value = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| { + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::CallbackSubscriber::on_update(&*api_that_guard, api_value); + })?; + Ok(output_ok) + })()) + } + }, + ) +} +fn wire__crate__MobileConvexClient_action_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_action", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_name = ::sse_decode(&mut deserializer); + let api_args = + >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::MobileConvexClient::action(&*api_that_guard, api_name, api_args) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_mutation_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_mutation", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_name = ::sse_decode(&mut deserializer); + let api_args = + >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::MobileConvexClient::mutation( + &*api_that_guard, + api_name, + api_args, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_new_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_new", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_deployment_url = ::sse_decode(&mut deserializer); + let api_client_id = ::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let output_ok = Result::<_, ()>::Ok(crate::MobileConvexClient::new( + api_deployment_url, + api_client_id, + ))?; + Ok(output_ok) + })()) + }, + ) +} +fn wire__crate__MobileConvexClient_on_websocket_state_change_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_on_websocket_state_change", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_on_state_change = + decode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + ::sse_decode(&mut deserializer), + ); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::MobileConvexClient::on_websocket_state_change( + &*api_that_guard, + api_on_state_change, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_query_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_query", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_name = ::sse_decode(&mut deserializer); + let api_args = + >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::MobileConvexClient::query(&*api_that_guard, api_name, api_args) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_reconnect_now_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_reconnect_now", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_reason = ::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::MobileConvexClient::reconnect_now(&*api_that_guard, api_reason) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_set_auth_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_set_auth", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_token = >::sse_decode(&mut deserializer); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = + crate::MobileConvexClient::set_auth(&*api_that_guard, api_token) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_set_auth_with_refresh_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_set_auth_with_refresh", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_fetch_token = decode_DartFn_Inputs__Output_opt_String_AnyhowException( + ::sse_decode(&mut deserializer), + ); + let api_on_auth_change = decode_DartFn_Inputs_bool_Output_unit_AnyhowException( + ::sse_decode(&mut deserializer), + ); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::MobileConvexClient::set_auth_with_refresh( + &*api_that_guard, + api_fetch_token, + api_on_auth_change, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__MobileConvexClient_subscribe_impl( + port_: flutter_rust_bridge::for_generated::MessagePort, + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_async::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "MobileConvexClient_subscribe", + port: Some(port_), + mode: flutter_rust_bridge::for_generated::FfiCallMode::Normal, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + let api_name = ::sse_decode(&mut deserializer); + let api_args = + >::sse_decode(&mut deserializer); + let api_on_update = decode_DartFn_Inputs_String_Output_unit_AnyhowException( + ::sse_decode(&mut deserializer), + ); + let api_on_error = decode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + ::sse_decode(&mut deserializer), + ); + deserializer.end(); + move |context| async move { + transform_result_sse::<_, crate::ClientError>( + (move || async move { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order( + vec![flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + )], + ); + for i in decode_indices_ { + match i { + 0 => { + api_that_guard = + Some(api_that.lockable_decode_async_ref().await) + } + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = crate::MobileConvexClient::subscribe( + &*api_that_guard, + api_name, + api_args, + api_on_update, + api_on_error, + ) + .await?; + Ok(output_ok) + })() + .await, + ) + } + }, + ) +} +fn wire__crate__SubscriptionHandle_cancel_impl( + ptr_: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len_: i32, + data_len_: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + FLUTTER_RUST_BRIDGE_HANDLER.wrap_sync::( + flutter_rust_bridge::for_generated::TaskInfo { + debug_name: "SubscriptionHandle_cancel", + port: None, + mode: flutter_rust_bridge::for_generated::FfiCallMode::Sync, + }, + move || { + let message = unsafe { + flutter_rust_bridge::for_generated::Dart2RustMessageSse::from_wire( + ptr_, + rust_vec_len_, + data_len_, + ) + }; + let mut deserializer = + flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let api_that = , + >>::sse_decode(&mut deserializer); + deserializer.end(); + transform_result_sse::<_, ()>((move || { + let mut api_that_guard = None; + let decode_indices_ = + flutter_rust_bridge::for_generated::lockable_compute_decode_order(vec![ + flutter_rust_bridge::for_generated::LockableOrderInfo::new( + &api_that, 0, false, + ), + ]); + for i in decode_indices_ { + match i { + 0 => api_that_guard = Some(api_that.lockable_decode_sync_ref()), + _ => unreachable!(), + } + } + let api_that_guard = api_that_guard.unwrap(); + let output_ok = Result::<_, ()>::Ok({ + crate::SubscriptionHandle::cancel(&*api_that_guard); + })?; + Ok(output_ok) + })()) + }, + ) +} + +// Section: related_funcs + +fn decode_DartFn_Inputs_String_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(String) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: String) -> () { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: String| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} +fn decode_DartFn_Inputs_String_opt_String_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(String, Option) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: String, + arg1: Option, + ) -> () { + let args = vec![ + arg0.into_into_dart().into_dart(), + arg1.into_into_dart().into_dart(), + ]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: String, arg1: Option| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + arg1, + )) + } +} +fn decode_DartFn_Inputs__Output_opt_String_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn() -> flutter_rust_bridge::DartFnFuture> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque) -> Option { + let args = vec![]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move || { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body(dart_opaque.clone())) + } +} +fn decode_DartFn_Inputs_bool_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(bool) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body(dart_opaque: flutter_rust_bridge::DartOpaque, arg0: bool) -> () { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: bool| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} +fn decode_DartFn_Inputs_web_socket_connection_state_Output_unit_AnyhowException( + dart_opaque: flutter_rust_bridge::DartOpaque, +) -> impl Fn(crate::WebSocketConnectionState) -> flutter_rust_bridge::DartFnFuture<()> { + use flutter_rust_bridge::IntoDart; + + async fn body( + dart_opaque: flutter_rust_bridge::DartOpaque, + arg0: crate::WebSocketConnectionState, + ) -> () { + let args = vec![arg0.into_into_dart().into_dart()]; + let message = FLUTTER_RUST_BRIDGE_HANDLER + .dart_fn_invoke(dart_opaque, args) + .await; + + let mut deserializer = flutter_rust_bridge::for_generated::SseDeserializer::new(message); + let action = deserializer.cursor.read_u8().unwrap(); + let ans = match action { + 0 => std::result::Result::Ok(<()>::sse_decode(&mut deserializer)), + 1 => std::result::Result::Err( + ::sse_decode(&mut deserializer), + ), + _ => unreachable!(), + }; + deserializer.end(); + let ans = ans.expect("Dart throws exception but Rust side assume it is not failable"); + ans + } + + move |arg0: crate::WebSocketConnectionState| { + flutter_rust_bridge::for_generated::convert_into_dart_fn_future(body( + dart_opaque.clone(), + arg0, + )) + } +} +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); +flutter_rust_bridge::frb_generated_moi_arc_impl_value!( + flutter_rust_bridge::for_generated::RustAutoOpaqueInner +); + +// Section: dart2rust + +impl SseDecode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::anyhow::anyhow!("{}", inner); + } +} + +impl SseDecode for AuthHandle { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for CallbackSubscriber { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for CallbackSubscriberDartFn { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for MobileConvexClient { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for SubscriptionHandle { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = , + >>::sse_decode(deserializer); + return flutter_rust_bridge::for_generated::rust_auto_opaque_decode_owned(inner); + } +} + +impl SseDecode for flutter_rust_bridge::DartOpaque { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return unsafe { flutter_rust_bridge::for_generated::sse_decode_dart_opaque(inner) }; + } +} + +impl SseDecode for std::collections::HashMap { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return inner.into_iter().collect(); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi< + flutter_rust_bridge::for_generated::RustAutoOpaqueInner, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return decode_rust_opaque_moi(inner); + } +} + +impl SseDecode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = >::sse_decode(deserializer); + return String::from_utf8(inner).unwrap(); + } +} + +impl SseDecode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() != 0 + } +} + +impl SseDecode for crate::ClientError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut tag_ = ::sse_decode(deserializer); + match tag_ { + 0 => { + let mut var_msg = ::sse_decode(deserializer); + return crate::ClientError::InternalError { msg: var_msg }; + } + 1 => { + let mut var_data = ::sse_decode(deserializer); + return crate::ClientError::ConvexError { data: var_data }; + } + 2 => { + let mut var_msg = ::sse_decode(deserializer); + return crate::ClientError::ServerError { msg: var_msg }; + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseDecode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i32::().unwrap() + } +} + +impl SseDecode for isize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_i64::().unwrap() as _ + } +} + +impl SseDecode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Vec<(String, String)> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut len_ = ::sse_decode(deserializer); + let mut ans_ = vec![]; + for idx_ in 0..len_ { + ans_.push(<(String, String)>::sse_decode(deserializer)); + } + return ans_; + } +} + +impl SseDecode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + if (::sse_decode(deserializer)) { + return Some(::sse_decode(deserializer)); + } else { + return None; + } + } +} + +impl SseDecode for (String, String) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut var_field0 = ::sse_decode(deserializer); + let mut var_field1 = ::sse_decode(deserializer); + return (var_field0, var_field1); + } +} + +impl SseDecode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u8().unwrap() + } +} + +impl SseDecode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self {} +} + +impl SseDecode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + deserializer.cursor.read_u64::().unwrap() as _ + } +} + +impl SseDecode for crate::WebSocketConnectionState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_decode(deserializer: &mut flutter_rust_bridge::for_generated::SseDeserializer) -> Self { + let mut inner = ::sse_decode(deserializer); + return match inner { + 0 => crate::WebSocketConnectionState::Connected, + 1 => crate::WebSocketConnectionState::Connecting, + _ => unreachable!("Invalid variant for WebSocketConnectionState: {}", inner), + }; + } +} + +fn pde_ffi_dispatcher_primary_impl( + func_id: i32, + port: flutter_rust_bridge::for_generated::MessagePort, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 3 => wire__crate__CallbackSubscriberDartFn_on_error_impl(port, ptr, rust_vec_len, data_len), + 4 => { + wire__crate__CallbackSubscriberDartFn_on_update_impl(port, ptr, rust_vec_len, data_len) + } + 5 => wire__crate__CallbackSubscriber_on_error_impl(port, ptr, rust_vec_len, data_len), + 6 => wire__crate__CallbackSubscriber_on_update_impl(port, ptr, rust_vec_len, data_len), + 7 => wire__crate__MobileConvexClient_action_impl(port, ptr, rust_vec_len, data_len), + 8 => wire__crate__MobileConvexClient_mutation_impl(port, ptr, rust_vec_len, data_len), + 10 => wire__crate__MobileConvexClient_on_websocket_state_change_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 11 => wire__crate__MobileConvexClient_query_impl(port, ptr, rust_vec_len, data_len), + 12 => wire__crate__MobileConvexClient_reconnect_now_impl(port, ptr, rust_vec_len, data_len), + 13 => wire__crate__MobileConvexClient_set_auth_impl(port, ptr, rust_vec_len, data_len), + 14 => wire__crate__MobileConvexClient_set_auth_with_refresh_impl( + port, + ptr, + rust_vec_len, + data_len, + ), + 15 => wire__crate__MobileConvexClient_subscribe_impl(port, ptr, rust_vec_len, data_len), + _ => unreachable!(), + } +} + +fn pde_ffi_dispatcher_sync_impl( + func_id: i32, + ptr: flutter_rust_bridge::for_generated::PlatformGeneralizedUint8ListPtr, + rust_vec_len: i32, + data_len: i32, +) -> flutter_rust_bridge::for_generated::WireSyncRust2DartSse { + // Codec=Pde (Serialization + dispatch), see doc to use other codecs + match func_id { + 1 => wire__crate__AuthHandle_dispose_impl(ptr, rust_vec_len, data_len), + 2 => wire__crate__AuthHandle_is_authenticated_impl(ptr, rust_vec_len, data_len), + 9 => wire__crate__MobileConvexClient_new_impl(ptr, rust_vec_len, data_len), + 16 => wire__crate__SubscriptionHandle_cancel_impl(ptr, rust_vec_len, data_len), + _ => unreachable!(), + } +} + +// Section: rust2dart + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for FrbWrapper {} + +impl flutter_rust_bridge::IntoIntoDart> for AuthHandle { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> for CallbackSubscriber { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> + for CallbackSubscriberDartFn +{ + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> for MobileConvexClient { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for FrbWrapper { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self.0) + .into_dart() + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for FrbWrapper +{ +} + +impl flutter_rust_bridge::IntoIntoDart> for SubscriptionHandle { + fn into_into_dart(self) -> FrbWrapper { + self.into() + } +} + +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::ClientError { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + crate::ClientError::InternalError { msg } => { + [0.into_dart(), msg.into_into_dart().into_dart()].into_dart() + } + crate::ClientError::ConvexError { data } => { + [1.into_dart(), data.into_into_dart().into_dart()].into_dart() + } + crate::ClientError::ServerError { msg } => { + [2.into_dart(), msg.into_into_dart().into_dart()].into_dart() + } + _ => { + unimplemented!(""); + } + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive for crate::ClientError {} +impl flutter_rust_bridge::IntoIntoDart for crate::ClientError { + fn into_into_dart(self) -> crate::ClientError { + self + } +} +// Codec=Dco (DartCObject based), see doc to use other codecs +impl flutter_rust_bridge::IntoDart for crate::WebSocketConnectionState { + fn into_dart(self) -> flutter_rust_bridge::for_generated::DartAbi { + match self { + Self::Connected => 0.into_dart(), + Self::Connecting => 1.into_dart(), + _ => unreachable!(), + } + } +} +impl flutter_rust_bridge::for_generated::IntoDartExceptPrimitive + for crate::WebSocketConnectionState +{ +} +impl flutter_rust_bridge::IntoIntoDart + for crate::WebSocketConnectionState +{ + fn into_into_dart(self) -> crate::WebSocketConnectionState { + self + } +} + +impl SseEncode for flutter_rust_bridge::for_generated::anyhow::Error { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(format!("{:?}", self), serializer); + } +} + +impl SseEncode for AuthHandle { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for CallbackSubscriber { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for CallbackSubscriberDartFn { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + , + >>::sse_encode( + flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), + serializer, + ); + } +} + +impl SseEncode for MobileConvexClient { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for SubscriptionHandle { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >>::sse_encode(flutter_rust_bridge::for_generated::rust_auto_opaque_encode::<_, MoiArc<_>>(self), serializer); + } +} + +impl SseEncode for flutter_rust_bridge::DartOpaque { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.encode(), serializer); + } +} + +impl SseEncode for std::collections::HashMap { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_iter().collect(), serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi< + flutter_rust_bridge::for_generated::RustAutoOpaqueInner, + > +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode + for RustOpaqueMoi> +{ + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + let (ptr, size) = self.sse_encode_raw(); + ::sse_encode(ptr, serializer); + ::sse_encode(size, serializer); + } +} + +impl SseEncode for String { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + >::sse_encode(self.into_bytes(), serializer); + } +} + +impl SseEncode for bool { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self as _).unwrap(); + } +} + +impl SseEncode for crate::ClientError { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + match self { + crate::ClientError::InternalError { msg } => { + ::sse_encode(0, serializer); + ::sse_encode(msg, serializer); + } + crate::ClientError::ConvexError { data } => { + ::sse_encode(1, serializer); + ::sse_encode(data, serializer); + } + crate::ClientError::ServerError { msg } => { + ::sse_encode(2, serializer); + ::sse_encode(msg, serializer); + } + _ => { + unimplemented!(""); + } + } + } +} + +impl SseEncode for i32 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_i32::(self).unwrap(); + } +} + +impl SseEncode for isize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer + .cursor + .write_i64::(self as _) + .unwrap(); + } +} + +impl SseEncode for Vec { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + ::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Vec<(String, String)> { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.len() as _, serializer); + for item in self { + <(String, String)>::sse_encode(item, serializer); + } + } +} + +impl SseEncode for Option { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.is_some(), serializer); + if let Some(value) = self { + ::sse_encode(value, serializer); + } + } +} + +impl SseEncode for (String, String) { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode(self.0, serializer); + ::sse_encode(self.1, serializer); + } +} + +impl SseEncode for u8 { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer.cursor.write_u8(self).unwrap(); + } +} + +impl SseEncode for () { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) {} +} + +impl SseEncode for usize { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + serializer + .cursor + .write_u64::(self as _) + .unwrap(); + } +} + +impl SseEncode for crate::WebSocketConnectionState { + // Codec=Sse (Serialization based), see doc to use other codecs + fn sse_encode(self, serializer: &mut flutter_rust_bridge::for_generated::SseSerializer) { + ::sse_encode( + match self { + crate::WebSocketConnectionState::Connected => 0, + crate::WebSocketConnectionState::Connecting => 1, + _ => { + unimplemented!(""); + } + }, + serializer, + ); + } +} + +#[cfg(not(target_family = "wasm"))] +mod io { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.11.1. + + // Section: imports + + use super::*; + use crate::QuerySubscriber; + use crate::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_io!(); + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[unsafe(no_mangle)] + pub extern "C" fn frbgen_convex_flutter_rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(not(target_family = "wasm"))] +pub use io::*; + +/// cbindgen:ignore +#[cfg(target_family = "wasm")] +mod web { + // This file is automatically generated, so please do not edit it. + // @generated by `flutter_rust_bridge`@ 2.11.1. + + // Section: imports + + use super::*; + use crate::QuerySubscriber; + use crate::*; + use flutter_rust_bridge::for_generated::byteorder::{ + NativeEndian, ReadBytesExt, WriteBytesExt, + }; + use flutter_rust_bridge::for_generated::wasm_bindgen; + use flutter_rust_bridge::for_generated::wasm_bindgen::prelude::*; + use flutter_rust_bridge::for_generated::{transform_result_dco, Lifetimeable, Lockable}; + use flutter_rust_bridge::{Handler, IntoIntoDart}; + + // Section: boilerplate + + flutter_rust_bridge::frb_generated_boilerplate_web!(); + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerAuthHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriber( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerCallbackSubscriberDartFn( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerMobileConvexClient( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_increment_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::increment_strong_count(ptr as _); + } + + #[wasm_bindgen] + pub fn rust_arc_decrement_strong_count_RustOpaque_flutter_rust_bridgefor_generatedRustAutoOpaqueInnerSubscriptionHandle( + ptr: *const std::ffi::c_void, + ) { + MoiArc::>::decrement_strong_count(ptr as _); + } +} +#[cfg(target_family = "wasm")] +pub use web::*; diff --git a/third_party/convex_flutter/rust/src/lib.rs b/third_party/convex_flutter/rust/src/lib.rs new file mode 100644 index 00000000..289ab6d0 --- /dev/null +++ b/third_party/convex_flutter/rust/src/lib.rs @@ -0,0 +1,564 @@ +mod frb_generated; +use std::{ + collections::{BTreeMap, HashMap}, + sync::{ + atomic::{AtomicBool, AtomicU64, Ordering}, + Arc, + }, +}; + +#[cfg(debug_assertions)] +use android_logger::Config; +use async_once_cell::OnceCell; +use convex::{ + AuthTokenFetcher, + AuthenticationToken, + ConvexClient, + ConvexClientBuilder, + FunctionResult, + Value, // Convex client and result types + WebSocketState as ConvexWebSocketState, +}; +use flutter_rust_bridge::{frb, DartFnFuture}; +use futures::{ + channel::oneshot::{self, Sender}, + pin_mut, select_biased, FutureExt, StreamExt, +}; +use log::debug; // Logging for debugging purposes +#[cfg(debug_assertions)] +use log::LevelFilter; +use parking_lot::Mutex; +// Custom error type for Convex client operations, exposed to Dart. +#[derive(Debug, thiserror::Error)] +#[frb] +pub enum ClientError { + /// An internal error within the mobile Convex client. + #[error("InternalError: {msg}")] + InternalError { msg: String }, + /// An application-specific error from a remote Convex backend function. + #[error("ConvexError: {data}")] + ConvexError { data: String }, + /// An unexpected server-side error from a remote Convex function. + #[error("ServerError: {msg}")] + ServerError { msg: String }, +} + +impl From for ClientError { + fn from(value: anyhow::Error) -> Self { + Self::InternalError { + msg: value.to_string(), + } + } +} + +/// WebSocket connection state exposed to Flutter/Dart. +/// +/// This enum represents the current state of the WebSocket connection +/// to the Convex backend, allowing real-time connection monitoring. +#[derive(Debug, Clone)] +#[frb] +pub enum WebSocketConnectionState { + /// The WebSocket is open and connected to the Convex backend. + Connected, + /// The WebSocket is closed and is connecting or reconnecting. + Connecting, +} + +impl From for WebSocketConnectionState { + fn from(state: ConvexWebSocketState) -> Self { + match state { + ConvexWebSocketState::Connected => WebSocketConnectionState::Connected, + ConvexWebSocketState::Connecting => WebSocketConnectionState::Connecting, + } + } +} + +/// Trait defining the interface for handling subscription updates. +// Not directly exposed to Dart, used internally by subscribers. +pub trait QuerySubscriber: Send + Sync { + fn on_update(&self, value: String); // Called when a new update is received + fn on_error(&self, message: String, value: Option); // Called on error with optional value +} + +/// Adapter struct to implement QuerySubscriber using Dart callbacks. +pub struct CallbackSubscriber { + on_update: Box, // Callback for updates + on_error: Box) + Send + Sync>, // Callback for errors +} + +impl QuerySubscriber for CallbackSubscriber { + fn on_update(&self, value: String) { + (self.on_update)(value); + } + + fn on_error(&self, message: String, value: Option) { + (self.on_error)(message, value); + } +} + +/// Opaque type for Dart, representing a subscription handle with cancellation. +#[frb(opaque)] +pub struct SubscriptionHandle { + cancel_sender: Arc>>>, // Sender to cancel the subscription +} + +impl SubscriptionHandle { + fn new(cancel_sender: Sender<()>) -> Self { + SubscriptionHandle { + cancel_sender: Arc::new(Mutex::new(Some(cancel_sender))), + } + } + + /// Cancels the subscription by sending a cancellation signal. + #[frb(sync)] + pub fn cancel(&self) { + if let Some(sender) = self.cancel_sender.lock().take() { + sender.send(()).unwrap(); + } + } +} + +/// Opaque type for Dart, representing an auth session handle with lifecycle management. +/// Used to control the token refresh loop and check authentication state. +#[frb(opaque)] +pub struct AuthHandle { + cancel_sender: Arc>>>, + is_authenticated: Arc, +} + +impl AuthHandle { + fn new(cancel_sender: Sender<()>, is_authenticated: Arc) -> Self { + AuthHandle { + cancel_sender: Arc::new(Mutex::new(Some(cancel_sender))), + is_authenticated, + } + } + + /// Disposes the auth session, stopping the token refresh loop and clearing authentication. + #[frb(sync)] + pub fn dispose(&self) { + if let Some(sender) = self.cancel_sender.lock().take() { + let _ = sender.send(()); + } + } + + /// Returns whether the user is currently authenticated. + #[frb(sync)] + pub fn is_authenticated(&self) -> bool { + self.is_authenticated.load(Ordering::SeqCst) + } +} + +/// Adapter for Dart functions as subscribers, handling async callbacks. +pub struct CallbackSubscriberDartFn { + on_update: Box DartFnFuture<()> + Send + Sync>, // Async update callback + on_error: Box) -> DartFnFuture<()> + Send + Sync>, // Async error callback +} + +impl QuerySubscriber for CallbackSubscriberDartFn { + fn on_update(&self, value: String) { + let future = (self.on_update)(value); + tokio::spawn(async move { + let _ = future.await; // Await the future, ignoring the result + }); + } + + fn on_error(&self, message: String, value: Option) { + let future = (self.on_error)(message, value); + tokio::spawn(async move { + let _ = future.await; + }); + } +} + +/// Main Convex client struct, opaque to Dart, managing connections and operations. +#[frb(opaque)] +pub struct MobileConvexClient { + deployment_url: String, // URL of the Convex deployment + client_id: String, // Client ID for authentication + client: OnceCell, // Lazy-initialized Convex client + rt: tokio::runtime::Runtime, // Tokio runtime for async operations + auth_generation: Arc, + // Channel sender for WebSocket state change notifications + state_change_sender: Arc>>>, +} + +impl MobileConvexClient { + /// Creates a new MobileConvexClient instance with the given deployment URL and client ID. + #[frb(sync)] + pub fn new(deployment_url: String, client_id: String) -> MobileConvexClient { + #[cfg(debug_assertions)] + android_logger::init_once(Config::default().with_max_level(LevelFilter::Error)); + let rt = tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap(); + MobileConvexClient { + deployment_url, + client_id, + client: OnceCell::new(), + rt, + auth_generation: Arc::new(AtomicU64::new(0)), + state_change_sender: Arc::new(Mutex::new(None)), + } + } + + /// Sets up WebSocket connection state change listener. + /// + /// Must be called BEFORE any queries/mutations to capture all state changes. + /// The callback will be invoked whenever the WebSocket transitions between + /// Connected and Connecting states. + /// + /// # Arguments + /// + /// * `on_state_change` - Async callback invoked when connection state changes + /// + /// # Example + /// + /// ```dart + /// await client.onWebsocketStateChange( + /// onStateChange: (state) async { + /// print('Connection state: ${state.name}'); + /// }, + /// ); + /// ``` + #[frb] + pub async fn on_websocket_state_change( + &self, + on_state_change: impl Fn(WebSocketConnectionState) -> DartFnFuture<()> + Send + Sync + 'static, + ) -> Result<(), ClientError> { + println!("RUST: on_websocket_state_change() called"); + + // Create tokio mpsc channel for receiving state changes from convex client + let (state_tx, mut state_rx) = tokio::sync::mpsc::channel::(10); + println!("RUST: Created mpsc channel for state changes"); + + // Store sender for use when initializing the client + { + let mut sender = self.state_change_sender.lock(); + *sender = Some(state_tx); + println!("RUST: Stored state_tx in state_change_sender"); + } + + // Spawn task to listen for state changes and call Dart callback + let on_state_change = Arc::new(on_state_change); + println!("RUST: Spawning listener task for state changes"); + self.rt.spawn(async move { + println!("RUST: Listener task started, waiting for state changes"); + while let Some(state) = state_rx.recv().await { + println!("RUST: Received state change from channel: {:?}", state); + let dart_state = WebSocketConnectionState::from(state); + println!("RUST: Converted to Dart state: {:?}", dart_state); + let callback = on_state_change.clone(); + let future = (callback)(dart_state); + println!("RUST: Calling Dart callback"); + let _ = future.await; + println!("RUST: Dart callback completed"); + } + println!("RUST: Listener task exiting (channel closed)"); + }); + + println!("RUST: on_websocket_state_change() returning"); + Ok(()) + } + + /// Retrieves or initializes a connected Convex client. + async fn connected_client(&self) -> anyhow::Result { + let url = self.deployment_url.clone(); + let state_sender = self.state_change_sender.lock().clone(); + + println!( + "RUST: connected_client() called with sender: {:?}", + state_sender.is_some() + ); + + self.client + .get_or_try_init(async { + let client_id = self.client_id.to_owned(); + + // Build client directly without spawning a task + // This ensures callback is registered BEFORE connection starts + println!("RUST: Building ConvexClient directly (no task spawn)"); + let mut builder = ConvexClientBuilder::new(url.as_str()).with_client_id(&client_id); + + // Register state change callback BEFORE building + if let Some(sender) = state_sender { + println!("RUST: Registering state change callback with builder"); + builder = builder.with_on_state_change(sender); + } else { + println!( + "RUST WARNING: No sender available - state changes will not be emitted" + ); + } + + println!("RUST: Calling builder.build() - connection will start now"); + let result = builder.build().await; + match &result { + Ok(_) => println!("RUST: ConvexClient built successfully"), + Err(e) => println!("RUST ERROR: Failed to build ConvexClient: {:?}", e), + } + result + }) + .await + .map(|client_ref| client_ref.clone()) + } + + /// Executes a query on the Convex backend. + #[frb] + pub async fn query( + &self, + name: String, + args: HashMap, + ) -> Result { + let mut client = self.connected_client().await?; + debug!("got the client"); + let result = client.query(name.as_str(), parse_json_args(args)).await?; + debug!("got the result"); + handle_direct_function_result(result) + } + + /// Subscribes to real-time updates from a Convex query. + #[frb] + pub async fn subscribe( + &self, + name: String, + args: HashMap, + on_update: impl Fn(String) -> DartFnFuture<()> + Send + Sync + 'static, + on_error: impl Fn(String, Option) -> DartFnFuture<()> + Send + Sync + 'static, + ) -> Result { + let subscriber = Arc::new(CallbackSubscriberDartFn { + on_update: Box::new(on_update), + on_error: Box::new(on_error), + }); + self.internal_subscribe(name, args, subscriber) + .await + .map_err(Into::into) + } + + /// Internal method for subscription logic. + async fn internal_subscribe( + &self, + name: String, + args: HashMap, + subscriber: Arc, + ) -> anyhow::Result { + let mut client = self.connected_client().await?; + debug!("New subscription"); + let mut subscription = client + .subscribe(name.as_str(), parse_json_args(args)) + .await?; + let (cancel_sender, cancel_receiver) = oneshot::channel::<()>(); + self.rt.spawn(async move { + let cancel_fut = cancel_receiver.fuse(); + pin_mut!(cancel_fut); + loop { + select_biased! { + new_val = subscription.next().fuse() => { + let new_val = match new_val { + Some(val) => val, + None => { + log::warn!("Subscription stream ended for {}", &name); + break; + } + }; + match new_val { + FunctionResult::Value(value) => { + debug!("Updating with {value:?}"); + subscriber.on_update(serde_json::to_string( + &serde_json::Value::from(value), + ).unwrap()); + } + FunctionResult::ErrorMessage(message) => { + subscriber.on_error(message, None); + } + FunctionResult::ConvexError(error) => subscriber.on_error( + error.message, + Some(serde_json::ser::to_string( + &serde_json::Value::from(error.data), + ).unwrap()), + ), + } + } + _ = cancel_fut => { + break; + } + } + } + debug!("Subscription canceled"); + }); + Ok(SubscriptionHandle::new(cancel_sender)) + } + + /// Executes a mutation on the Convex backend. + #[frb] + pub async fn mutation( + &self, + name: String, + args: HashMap, + ) -> Result { + let result = self.internal_mutation(name, args).await?; + handle_direct_function_result(result) + } + + /// Internal method for mutation logic. + async fn internal_mutation( + &self, + name: String, + args: HashMap, + ) -> anyhow::Result { + let mut client = self.connected_client().await?; + self.rt + .spawn(async move { client.mutation(&name, parse_json_args(args)).await }) + .await? + } + + /// Executes an action on the Convex backend. + #[frb] + pub async fn action( + &self, + name: String, + args: HashMap, + ) -> Result { + debug!("Running action: {}", name); + let result = self.internal_action(name, args).await?; + debug!("Got action result: {:?}", result); + handle_direct_function_result(result) + } + + /// Internal method for action logic. + async fn internal_action( + &self, + name: String, + args: HashMap, + ) -> anyhow::Result { + let mut client = self.connected_client().await?; + debug!("Running action: {}", name); + self.rt + .spawn(async move { client.action(&name, parse_json_args(args)).await }) + .await? + } + + /// Sets authentication token for the client. + #[frb] + pub async fn set_auth(&self, token: Option) -> Result<(), ClientError> { + Ok(self.internal_set_auth(token).await?) + } + + /// Internal method for setting authentication. + async fn internal_set_auth(&self, token: Option) -> anyhow::Result<()> { + // Invalidate any older refresh handle before replacing its callback. + // A delayed disposal from that handle must not clear this auth state. + self.auth_generation.fetch_add(1, Ordering::SeqCst); + let mut client = self.connected_client().await?; + self.rt + .spawn(async move { client.set_auth(token).await }) + .await + .map_err(|e| e.into()) + } + + /// Forces a WebSocket reconnect while retaining current client state. + #[frb] + pub async fn reconnect_now(&self, reason: String) -> Result<(), ClientError> { + let mut client = self.connected_client().await?; + self.rt + .spawn(async move { client.reconnect_now(&reason).await }) + .await + .map_err(|e| ClientError::InternalError { msg: e.to_string() })?; + Ok(()) + } + + /// Sets authentication with token refresh on every WebSocket reconnect. + /// + /// The callback is owned by the upstream Convex client so authentication + /// and query state are replayed together after a disconnect. + /// + /// Returns an AuthHandle that can be used to dispose the auth session. + #[frb] + pub async fn set_auth_with_refresh( + &self, + fetch_token: impl Fn() -> DartFnFuture> + Send + Sync + 'static, + on_auth_change: impl Fn(bool) -> DartFnFuture<()> + Send + Sync + 'static, + ) -> Result { + let is_authenticated = Arc::new(AtomicBool::new(false)); + let (cancel_sender, cancel_receiver) = oneshot::channel::<()>(); + let generation = self.auth_generation.fetch_add(1, Ordering::SeqCst) + 1; + + let mut client = self.connected_client().await?; + let fetch_token = Arc::new(fetch_token); + let on_auth_change = Arc::new(on_auth_change); + let cancel_on_auth_change = on_auth_change.clone(); + let callback_is_authenticated = is_authenticated.clone(); + let callback: AuthTokenFetcher = Box::new(move |_force_refresh| { + let fetch_token = fetch_token.clone(); + let on_auth_change = on_auth_change.clone(); + let is_authenticated = callback_is_authenticated.clone(); + Box::pin(async move { + let token = (fetch_token)().await; + let next_is_authenticated = token.is_some(); + let changed = is_authenticated.swap(next_is_authenticated, Ordering::SeqCst) + != next_is_authenticated; + if changed { + let _ = (on_auth_change)(next_is_authenticated).await; + } + Ok(match token { + Some(token) => AuthenticationToken::User(token), + None => AuthenticationToken::None, + }) + }) + }); + client.set_auth_callback(Some(callback)).await; + + let cancel_is_authenticated = is_authenticated.clone(); + let cancel_auth_generation = self.auth_generation.clone(); + self.rt.spawn(async move { + let _ = cancel_receiver.await; + if cancel_auth_generation + .compare_exchange( + generation, + generation + 1, + Ordering::SeqCst, + Ordering::SeqCst, + ) + .is_err() + { + return; + } + let mut client = client.clone(); + client.set_auth_callback(None).await; + if cancel_is_authenticated.swap(false, Ordering::SeqCst) { + let _ = (cancel_on_auth_change)(false).await; + } + }); + + Ok(AuthHandle::new(cancel_sender, is_authenticated)) + } +} + +/// Utility function to parse HashMap arguments into Convex Value format. +fn parse_json_args(raw_args: HashMap) -> BTreeMap { + raw_args + .into_iter() + .map(|(k, v)| { + ( + k, + Value::try_from( + serde_json::from_str::(&v) + .expect("Invalid JSON data from FFI"), + ) + .expect("Invalid Convex data from FFI"), + ) + }) + .collect() +} + +/// Utility function to handle and serialize FunctionResult into a string or error. +fn handle_direct_function_result(result: FunctionResult) -> Result { + match result { + FunctionResult::Value(v) => serde_json::to_string(&serde_json::Value::from(v)) + .map_err(|e| ClientError::InternalError { msg: e.to_string() }), + FunctionResult::ConvexError(e) => Err(ClientError::ConvexError { + data: serde_json::ser::to_string(&serde_json::Value::from(e.data)).unwrap(), + }), + FunctionResult::ErrorMessage(msg) => Err(ClientError::ServerError { msg }), + } +} diff --git a/third_party/convex_flutter/test_driver/integration_test.dart b/third_party/convex_flutter/test_driver/integration_test.dart new file mode 100644 index 00000000..b38629cc --- /dev/null +++ b/third_party/convex_flutter/test_driver/integration_test.dart @@ -0,0 +1,3 @@ +import 'package:integration_test/integration_test_driver.dart'; + +Future main() => integrationDriver(); diff --git a/third_party/convex_flutter/windows/CMakeLists.txt b/third_party/convex_flutter/windows/CMakeLists.txt new file mode 100644 index 00000000..4c825e7a --- /dev/null +++ b/third_party/convex_flutter/windows/CMakeLists.txt @@ -0,0 +1,20 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "convex_flutter") +project(${PROJECT_NAME} LANGUAGES CXX) + +include("../cargokit/cmake/cargokit.cmake") +apply_cargokit(${PROJECT_NAME} ../rust convex_flutter "") + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(convex_flutter_bundled_libraries + "${${PROJECT_NAME}_cargokit_lib}" + PARENT_SCOPE +) diff --git a/third_party/convex_rs/.gitignore b/third_party/convex_rs/.gitignore new file mode 100644 index 00000000..dec8ba25 --- /dev/null +++ b/third_party/convex_rs/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +target/ +convex_local_backend*.sqlite3 +convex_local_storage/ +.DS_Store +# testing 123 diff --git a/third_party/convex_rs/.prettierrc b/third_party/convex_rs/.prettierrc new file mode 100644 index 00000000..473aecc5 --- /dev/null +++ b/third_party/convex_rs/.prettierrc @@ -0,0 +1,2 @@ +proseWrap: "always" +arrowParens: "avoid" diff --git a/third_party/convex_rs/CHANGELOG.md b/third_party/convex_rs/CHANGELOG.md new file mode 100644 index 00000000..da573d9d --- /dev/null +++ b/third_party/convex_rs/CHANGELOG.md @@ -0,0 +1,103 @@ +# 0.10.4 + +- Optimizations to `check_valid_field_name` in `sync_types` +- Fix for memory leak in query subscriptions + (https://github.com/get-convex/convex-rs/issues/15) +- Bump rust-version minimum from 1.80.1 to 1.85 + +# 0.10.3 + +- Fix for incorrect client state on WebSocket reconnect +- New `set_auth_callback` method on `ConvexClient` to allow token refresh on + WebSocket reconnect + +# 0.10.2 + +- Fix for deadlock between client and websocket worker tasks +- Update `tokio` dependency + +# 0.10.1 + +- Bump sync_types version and depend on it + +# 0.10.0 + +- Fix for panic in query subscriptions +- Bump rust-version minimum from 1.71.1 to 1.80.1 + +# 0.9.0 + +- Add `ConvexClientBuilder` pattern for constructing `ConvexClient` +- Add support for `on_state_change` for handling reconnects. +- Bump rust-version minimum from 1.65.0 to 1.71.1 +- Update `url` dependency. + +# 0.8.1 + +Remove native-tls-vendored dependency for tokio-tungstenite. Rely on requested +features instead. + +# 0.8.0 + +- Support for passing through a client_id to ConvexClient +- Dependency upgrades + +# 0.7.0 + +- Several dependency upgrades + +# 0.6.0 + +- Remove support for Set and Map Convex types. These types are deprecated. +- Add comprehensive support for ConvexError with `data` payload as part of the + `FunctionResult` enum. +- Better support for emitting loglines + +# 0.5.0 + +- Prelim support for ConvexError, encoded into an anyhow::Error. Eventual plan + is to expose a separate catchable type, but just getting something out + quickly. PRs accepted! + +# 0.4.0 + +- Expose an alternate cleaner JSON export format on Value. The clean format is + lossy in some cases (eg both integers and strings are encoded as JSON + strings). +- Expose native-tls-vendored feature + +# 0.3.1 + +- Fix compilation with `--features=testing` +- Minor syntactic changes to quickstart + +# 0.3.0 + +- Remove `Value::Id` since document IDs are `Value::String`s for Convex + functions starting from NPM version 0.17 +- Minor improvements to convex_chat_client example +- Minor improvements in convex_sync_types + +# 0.2.0 + +- BUGFIX: Client occasionally used to get stuck in a hot loop after network + disconnect. +- Tweak backoff params for better performance across network disconnect. +- Minor improvements to convex_chat_client example +- Minor fix to running tests +- Bump tokio-tungstenite to 0.18 +- Minor improvements in convex_sync_types + +# 0.1.2 + +Yanked and re-released as 0.2.0 + +# 0.1.1 + +- Fix race between mutation result and dropping a subscription. +- Minor logging/error message improvements. + +# 0.1.0 + +- Initial release. +- Support for queries, subscriptions, mutations, actions diff --git a/third_party/convex_rs/CONTRIBUTING.md b/third_party/convex_rs/CONTRIBUTING.md new file mode 100644 index 00000000..0d5fd9f7 --- /dev/null +++ b/third_party/convex_rs/CONTRIBUTING.md @@ -0,0 +1,38 @@ +# Contributing + +Contributions are welcome! + +Please share any general questions, feature requests, or product feedback in our +[Convex Discord Community](https://convex.dev/community). We're particularly +excited to see what you build on Convex! + +Please ensure that rust code is formatted with +[cargo fmt](https://github.com/rust-lang/rustfmt) and markdown files are +formatted with [prettier](https://prettier.io/). + +Run tests with + +``` +cargo test -p {crate} +``` + +Convex is a fast moving project developed by a dedicated team. We're excited to +contribute to the community by releasing this code, but we want to manage +expectations as well. + +- We are a small company with a lot of product surface area. +- We value a cohesive developer experience for folks building applications + across all of our languages and platforms. +- We value transparency in how we operate. + +We're excited for community PRs. Be aware we may not get to it for a while. +Smaller PRs that only affect documentation/comments are easier to review and +integrate. For any larger or more fundamental changes, get in touch with us on +Discord before you put in too much work to see if it's consistent with our short +term plan. We think carefully about how our APIs contribute to a cohesive +product, so chatting up front goes a long way. + +# Docs contributions + +Docs are located in the `npm-packages/docs` directory. See the README there for +more info. diff --git a/third_party/convex_rs/Cargo.lock b/third_party/convex_rs/Cargo.lock new file mode 100644 index 00000000..13d25da1 --- /dev/null +++ b/third_party/convex_rs/Cargo.lock @@ -0,0 +1,2145 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" + +[[package]] +name = "archery" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae2ed21cd55021f05707a807a5fc85695dafb98832921f6cfa06db67ca5b869" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" + +[[package]] +name = "bitmaps" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703642b98a00b3b90513279a8ede3fcfa479c126c5fb46e78f3051522f021403" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytemuck" +version = "1.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37521ac7aabe3d13122dc382493e20c9416f299d2ccd5b3a5340a2570cdeb0f3" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "convex" +version = "0.10.4" +dependencies = [ + "anyhow", + "async-trait", + "base64 0.13.1", + "bytes", + "colored", + "convex_sync_types", + "dotenvy", + "futures", + "imbl", + "maplit", + "parking_lot", + "pretty_assertions", + "proptest", + "proptest-derive", + "rand 0.9.0", + "serde_json", + "thiserror", + "tokio", + "tokio-stream", + "tokio-tungstenite", + "tracing", + "tracing-subscriber", + "url", + "uuid", +] + +[[package]] +name = "convex_sync_types" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cba8235188b091cc50205a436bf6505cb386be208263fecdb4b62bd2b3c90a0a" +dependencies = [ + "anyhow", + "base64 0.13.1", + "bytes", + "derive_more", + "headers", + "pretty_assertions", + "proptest", + "proptest-derive", + "rand 0.9.0", + "serde", + "serde_json", + "strum", + "uuid", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "derive_more" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.108", + "unicode-xid", +] + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "find-msvc-tools" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.13.3+wasi-0.2.2", + "windows-targets 0.52.6", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" + +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "imbl" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e525189e5f603908d0c6e0d402cb5de9c4b2c8866151fabc4ebd771ed2630a2e" +dependencies = [ + "archery", + "bitmaps", + "imbl-sized-chunks", + "rand_core 0.9.1", + "rand_xoshiro", + "version_check", + "wide", +] + +[[package]] +name = "imbl-sized-chunks" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f4241005618a62f8d57b2febd02510fb96e0137304728543dfc5fd6f052c22d" +dependencies = [ + "bitmaps", +] + +[[package]] +name = "indexmap" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.180" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" + +[[package]] +name = "libm" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "litemap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" + +[[package]] +name = "lock_api" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework 2.8.2", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-src" +version = "300.2.3+3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cff92b6f71555b61bb9315f7c64da3ca43d87531622120fea0195fc761b4843" +dependencies = [ + "cc", +] + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "openssl-src", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "parking_lot" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_assertions" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755" +dependencies = [ + "ctor", + "diff", + "output_vt100", + "yansi", +] + +[[package]] +name = "proc-macro2" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e0f6df8eaa422d97d72edcd152e1451618fed47fabbdbd5a8864167b1d4aff7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4c2511913b88df1637da85cc8d96ec8e43a3f8bb8ccb71ee1ac240d6f3df58d" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags 2.10.0", + "lazy_static", + "num-traits", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "proptest-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff7ff745a347b87471d859a377a9a404361e7efc2a971d73424a6d183c0fc77" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.1", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.1", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_core" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88e0da7a2c97baa202165137c158d0a2e824ac465d13d81046727b34cb247d3" +dependencies = [ + "getrandom 0.3.1", + "zerocopy", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core 0.6.4", +] + +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.1", +] + +[[package]] +name = "redox_syscall" +version = "0.5.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "regex-automata" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustix" +version = "0.38.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a78891ee6bf2340288408954ac787aa063d8e8817e9f53abb37c695c6d834ef6" +dependencies = [ + "bitflags 2.10.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework 3.2.0", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "safe_arch" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.3", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.0", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "serde_json" +version = "1.0.145" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "ryu", + "serde", + "serde_core", +] + +[[package]] +name = "sha1" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "socket2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233504af464074f9d066d7b5416c5f9b894a5862a6506e306f7b816cdd6f1807" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.108", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da58917d35242480a05c2897064da0a80589a2a0476c9a3f2fdc83b53502e917" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tempfile" +version = "3.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" +dependencies = [ + "cfg-if", + "fastrand", + "getrandom 0.2.15", + "once_cell", + "rustix", + "windows-sys 0.59.0", +] + +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" +dependencies = [ + "bytes", + "libc", + "mio", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c55a2eff8b69ce66c84f85e1da1c233edc36ceb85a2058d11b0d6a3c7e7569c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25a406cddcc431a75d3d9afc6a7c0f7428d4891dd973e4d54c56b46127bf857" +dependencies = [ + "futures-util", + "log", + "native-tls", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tungstenite", + "webpki-roots 0.26.11", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f30143827ddab0d256fd843b7a66d164e9f271cfa0dde49142c5ca0ca291f1e" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tungstenite" +version = "0.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" +dependencies = [ + "bytes", + "data-encoding", + "http", + "httparse", + "log", + "native-tls", + "rand 0.9.0", + "rustls", + "rustls-pki-types", + "sha1", + "thiserror", + "url", + "utf-8", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +dependencies = [ + "getrandom 0.2.15", + "serde", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi" +version = "0.13.3+wasi-0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" +dependencies = [ + "wit-bindgen-rt", +] + +[[package]] +name = "webpki-roots" +version = "0.26.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521bc38abb08001b01866da9f51eb7c5d647a19260e00054a8c7fd5f9e57f7a9" +dependencies = [ + "webpki-roots 1.0.6", +] + +[[package]] +name = "webpki-roots" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "wide" +version = "0.7.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce5da8ecb62bcd8ec8b7ea19f69a51275e91299be594ea5cc6ef7819e16cd03" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "wit-bindgen-rt" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" +dependencies = [ + "bitflags 2.10.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yansi" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dde3bb8c68a8f3f1ed4ac9221aad6b10cece3e60a8e2ea54a6a2dec806d0084c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea57037071898bf96a6da35fd626f4f27e9cee3ead2a6c703cf09d472b2e700" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] + +[[package]] +name = "zerofrom" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.108", +] diff --git a/third_party/convex_rs/Cargo.toml b/third_party/convex_rs/Cargo.toml new file mode 100644 index 00000000..41e09222 --- /dev/null +++ b/third_party/convex_rs/Cargo.toml @@ -0,0 +1,180 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + +[package] +edition = "2021" +rust-version = "1.85" +name = "convex" +version = "0.10.4" +authors = ["Convex, Inc. "] +build = false +autolib = false +autobins = false +autoexamples = false +autotests = false +autobenches = false +description = "Client library for Convex (convex.dev)" +homepage = "https://www.convex.dev/" +readme = "README.md" +license = "Apache-2.0" +repository = "https://github.com/get-convex/convex-rs" +resolver = "2" + +[features] +default = ["native-tls-vendored"] +native-tls = ["tokio-tungstenite/native-tls"] +native-tls-vendored = ["tokio-tungstenite/native-tls-vendored"] +rustls-tls-native-roots = ["tokio-tungstenite/rustls-tls-native-roots"] +rustls-tls-webpki-roots = ["tokio-tungstenite/rustls-tls-webpki-roots"] +testing = [ + "convex_sync_types/testing", + "proptest", + "proptest-derive", + "parking_lot", +] + +[lib] +name = "convex" +path = "src/lib.rs" + +[[example]] +name = "convex_chat_client" +path = "examples/convex_chat_client.rs" + +[[example]] +name = "quickstart" +path = "examples/quickstart/main.rs" + +[dependencies.anyhow] +version = "1" + +[dependencies.async-trait] +version = "0.1" + +[dependencies.base64] +version = "0.13" + +[dependencies.bytes] +version = "1.6.0" + +[dependencies.convex_sync_types] +version = "=0.10.4" + +[dependencies.futures] +version = "0.3" + +[dependencies.imbl] +version = "7.0.0" + +[dependencies.parking_lot] +version = "0.12" +features = ["hardware-lock-elision"] +optional = true + +[dependencies.proptest] +version = "1" +optional = true + +[dependencies.proptest-derive] +version = "0.5.0" +optional = true + +[dependencies.rand] +version = "0.9" + +[dependencies.serde_json] +version = "1" +features = [ + "float_roundtrip", + "preserve_order", + "raw_value", +] + +[dependencies.thiserror] +version = "2" + +[dependencies.tokio] +version = "1.47.1" +features = ["full"] + +[dependencies.tokio-stream] +version = "0.1" +features = [ + "io-util", + "sync", +] + +[dependencies.tokio-tungstenite] +version = "0.28.0" +features = ["url"] + +[dependencies.tracing] +version = "0.1" + +[dependencies.url] +version = "2.5.4" + +[dependencies.uuid] +version = "1.6" +features = [ + "serde", + "v4", +] + +[dev-dependencies.colored] +version = "3" + +[dev-dependencies.convex_sync_types] +version = "=0.10.4" +features = ["testing"] + +[dev-dependencies.dotenvy] +version = "0.15.7" + +[dev-dependencies.maplit] +version = "1" + +[dev-dependencies.parking_lot] +version = "0.12" +features = ["hardware-lock-elision"] + +[dev-dependencies.pretty_assertions] +version = "1" + +[dev-dependencies.proptest] +version = "1" + +[dev-dependencies.proptest-derive] +version = "0.5.0" + +[dev-dependencies.tracing-subscriber] +version = "0.3.17" +features = ["env-filter"] + +[lints.clippy] +await_holding_lock = "warn" +await_holding_refcell_ref = "warn" +large_enum_variant = "allow" +manual_is_multiple_of = "allow" +manual_map = "allow" +new_without_default = "allow" +op_ref = "allow" +ptr_arg = "allow" +result_large_err = "allow" +single_match = "allow" +too_many_arguments = "allow" +type_complexity = "allow" +upper_case_acronyms = "allow" +useless_format = "allow" +useless_vec = "allow" + +[lints.rust] +unused_extern_crates = "warn" diff --git a/third_party/convex_rs/Cargo.toml.orig b/third_party/convex_rs/Cargo.toml.orig new file mode 100644 index 00000000..2cfc9c31 --- /dev/null +++ b/third_party/convex_rs/Cargo.toml.orig @@ -0,0 +1,71 @@ +[package] +name = "convex" +description = "Client library for Convex (convex.dev)" +authors = [ "Convex, Inc. " ] +version = "0.10.4" +edition = "2021" +rust-version = "1.85" +resolver = "2" +license = "Apache-2.0" +repository = "https://github.com/get-convex/convex-rs" +homepage = "https://www.convex.dev/" + +[features] +default = [ "native-tls-vendored" ] +native-tls = [ "tokio-tungstenite/native-tls" ] +native-tls-vendored = [ "tokio-tungstenite/native-tls-vendored" ] +rustls-tls-native-roots = [ "tokio-tungstenite/rustls-tls-native-roots" ] +rustls-tls-webpki-roots = [ "tokio-tungstenite/rustls-tls-webpki-roots" ] +testing = [ "convex_sync_types/testing", "proptest", "proptest-derive", "parking_lot" ] + +[dependencies] +anyhow = { version = "1" } +async-trait = { version = "0.1" } +base64 = { version = "0.13" } +bytes = { version = "1.6.0" } +convex_sync_types = { path = "./sync_types", version = "=0.10.4" } +futures = { version = "0.3" } +imbl = { version = "7.0.0" } +parking_lot = { optional = true, version = "0.12", features = [ "hardware-lock-elision" ] } +proptest = { optional = true, version = "1" } +proptest-derive = { optional = true, version = "0.5.0" } +rand = { version = "0.9" } +serde_json = { features = [ "float_roundtrip", "preserve_order", "raw_value" ], version = "1" } +thiserror = { version = "2" } +tokio = { features = [ "full" ], version = "1.47.1" } +tokio-stream = { features = [ "io-util", "sync" ], version = "0.1" } +tokio-tungstenite = { features = [ "url" ], version = "0.28.0" } +tracing = { version = "0.1" } +url = { version = "2.5.4" } +uuid = { features = [ "serde", "v4" ], version = "1.6" } + +[dev-dependencies] +colored = { version = "3" } +convex_sync_types = { path = "./sync_types", version = "=0.10.4", features = [ "testing" ] } +dotenvy = { version = "0.15.7" } +maplit = { version = "1" } +parking_lot = { version = "0.12", features = [ "hardware-lock-elision" ] } +pretty_assertions = { version = "1" } +proptest = { version = "1" } +proptest-derive = { version = "0.5.0" } +tracing-subscriber = { features = [ "env-filter" ], version = "0.3.17" } + +[lints.rust] +unused_extern_crates = "warn" + +[lints.clippy] +await_holding_lock = "warn" +await_holding_refcell_ref = "warn" +large_enum_variant = "allow" +manual_is_multiple_of = "allow" +manual_map = "allow" +new_without_default = "allow" +op_ref = "allow" +ptr_arg = "allow" +result_large_err = "allow" +single_match = "allow" +too_many_arguments = "allow" +type_complexity = "allow" +upper_case_acronyms = "allow" +useless_format = "allow" +useless_vec = "allow" diff --git a/third_party/convex_rs/ICARUS_PATCH.md b/third_party/convex_rs/ICARUS_PATCH.md new file mode 100644 index 00000000..78ad4956 --- /dev/null +++ b/third_party/convex_rs/ICARUS_PATCH.md @@ -0,0 +1,40 @@ +# Icarus convex-rs patch + +Source: the published `convex` 0.10.4 crate. + +Icarus adds a public `reconnect_now` request that tells the existing worker to +restart its WebSocket and replay current auth, subscriptions, and in-flight +mutations. The upstream client already owns that recovery path, but 0.10.4 does +not expose a way for a host application to trigger it. + +The local `convex_flutter` package uses this method for its documented manual +reconnect API. This keeps the runtime fault symmetric with Dartvex instead of +mistaking an authenticated health query for a socket reconnect. + +Icarus also separates server auth rejection from generic network failure in the +client worker. Auth rejection and the protocol-close errors caused by its old +socket retry the stored refresh callback every 250 ms. Buffered transitions from +the rejected socket cannot end recovery: the client first observes that the +callback produced a different token, then requires a server transition or +function response on that attempt. Other failures retain the upstream +randomized exponential backoff. This prevents a fresh token from sitting behind +a network backoff of up to 15 seconds without creating a hot reconnect loop +while the auth provider refreshes. + +The reconnect request carries that auth-recovery state to the WebSocket worker. +The client worker already supplies the 250 ms pacing, so the WebSocket worker +resets and skips its independent network backoff for those attempts. Without +that coordination, the two workers can each back off the same rejected socket +and still strand a fresh token for up to 15 seconds. + +Before a reconnect, the worker drains responses buffered by the connection it +is replacing. Auth rejection can otherwise be followed by a stale socket-close +failure during the retry delay; replaying that old failure on the new connection +starts a second backoff and can also apply old query-set transitions after the +client has rebuilt its versions. + +WebSocket reconnects now retain one session ID for the lifetime of the client +and increment `connection_count` after every successful socket open, including +client-requested reconnects. The published Rust client created a new session ID +for every socket and did not advance the count on clean reconnects, unlike the +Convex sync protocol's client-lifetime session and monotonic connection model. diff --git a/third_party/convex_rs/LICENSE b/third_party/convex_rs/LICENSE new file mode 100644 index 00000000..b615b0fc --- /dev/null +++ b/third_party/convex_rs/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2024 Convex, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/third_party/convex_rs/README.md b/third_party/convex_rs/README.md new file mode 100644 index 00000000..cfbbce12 --- /dev/null +++ b/third_party/convex_rs/README.md @@ -0,0 +1,63 @@ +# Convex + +The official Rust client for [Convex](https://convex.dev/). + +![GitHub](https://img.shields.io/github/license/get-convex/convex-rs) + +Convex is the backend application platform with everything you need to build +your product. + +This Rust client can write and read data from a Convex backend with queries, +mutations, and actions. Get up and running at +[docs.convex.dev](https://docs.convex.dev/introduction/). + +[Join us on Discord](https://www.convex.dev/community) to share what you're +working on or get your questions answered. + +# Installation + +Add the following to your `Cargo.toml` file + +```toml +[dependencies] +convex = "*" +``` + +# Example + +```rust +let mut client = ConvexClient::new(DEPLOYMENT_URL).await?; +let mut subscription = client.subscribe("getCounter", vec![]).await?; +while let Some(new_val) = subscription.next().await { + println!("Counter updated to {new_val:?}"); +} +``` + +# Documentation + +Check out the full convex documentation at +[docs.convex.dev](https://docs.convex.dev/introduction/) The rust API docs are +available on [docs.rs](https://docs.rs/convex/latest/convex/) + +# MSRV + +The Convex rust client works on stable rust 1.71.1 and higher. It also works on +nightly. + +# Debug Logging + +The Convex Rust Client uses the +[tracing](https://docs.rs/tracing/latest/tracing/) crate for logging. One common +way of initializing is via `tracing_subscriber`. Then, you can see debug logging +by running your program with `RUST_LOG=convex=debug`. + +```rust +tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); +``` + +By default, this will emit all logs, including internal logs from the client. +Logs from your Convex backend will show up under the `convex_logs` target at +Level=DEBUG. If you want to isolate just those logs, please refer to the +[tracing_subscriber documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/layer/index.html#filtering-with-layers). diff --git a/third_party/convex_rs/examples/convex_chat_client.rs b/third_party/convex_rs/examples/convex_chat_client.rs new file mode 100644 index 00000000..ec697a8d --- /dev/null +++ b/third_party/convex_rs/examples/convex_chat_client.rs @@ -0,0 +1,189 @@ +//! A client for the Convex tutorial chat app. +//! +//! Please run this Convex Chat client from an initialized Convex project. +//! Check out the https://docs.convex.dev/get-started - to get started. +//! +//! Once you've initialized a Convex project with the tutorial, run this +//! demo from inside the project's working directory. +//! +//! For example: +//! cd /path/to/convex-rs +//! cargo build --example convex_chat_client +//! cd /path/to/convex-demos/tutorial +//! /path/to/convex-rs/target/debug/examples/convex_chat_client + +use std::env; + +use colored::Colorize; +use convex::{ + ConvexClient, + FunctionResult, + Value, +}; +use futures::{ + pin_mut, + select_biased, + FutureExt, + StreamExt, +}; +use maplit::btreemap; +use tokio::sync::oneshot; + +const SETUP_MSG: &str = r" +Please run this Convex Chat client from an initialized Convex project. +Check out the https://docs.convex.dev/get-started - to get started. + +Once you've initialized a Convex project with the tutorial, run this +demo from inside the project's working directory. + +For example: +cd /path/to/convex-rs +cargo build --example convex_chat_clientt +cd /path/to/convex-demos/tutorial +/path/to/convex-rs/target/debug/examples/convex_chat_client + +"; + +#[tokio::main] +async fn main() -> anyhow::Result<()> { + tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .init(); + + // Load the tutorial's VITE_CONVEX_URL from the env file + dotenvy::from_filename(".env.local").ok(); + dotenvy::dotenv().ok(); + let Ok(deployment_url) = env::var("VITE_CONVEX_URL") else { + panic!("{SETUP_MSG}"); + }; + println!("Connecting to {deployment_url}"); + + // Client code used in thread #1 + let mut client = ConvexClient::new(&deployment_url).await?; + + // Client code used in thread #2 + let mut client_ = client.clone(); + + println!("{}", format!("Hi! What's your name?").red().bold()); + let mut sender = readline()?; + if sender.is_empty() { + sender = String::from("Anonymous Person"); + } + + let sender_clone = sender.clone(); + + // Thread listening for new messages (use_query demo) + let (cancel_sender, cancel_receiver) = oneshot::channel::<()>(); + let handle = tokio::spawn(async move { + let mut subscription = client + .subscribe("messages:list", btreemap! {}) + .await + .unwrap(); + + let cancel_fut = cancel_receiver.fuse(); + pin_mut!(cancel_fut); + loop { + select_biased! { + new_val = subscription.next().fuse() => { + let new_val = new_val.expect("Client dropped prematurely"); + println!( + "{}", + format!("---------------- Message History ----------------").yellow() + ); + if let FunctionResult::Value(Value::Array(array)) = new_val { + for item in array { + if let Value::Object(obj) = item { + if let Some(Value::String(str)) = obj.get("body") { + let author = match obj.get("author") { + Some(Value::String(name)) => name, + _ => "Anonymous Author", + }; + let author_string = if author == &sender_clone { + format!("{author}").yellow().bold() + } else { + format!("{author}").red().bold() + }; + println!("{author_string}: {str:?}"); + } + } + } + } + println!( + "{}", + format!("-------------- End Message History --------------").yellow() + ); + }, + _ = cancel_fut => { + break + }, + } + } + println!("Message listener closed"); + }); + + // Loop for sending messages + loop { + let line = readline()?; + let line = line.trim(); + if line.is_empty() { + continue; + } + + if line == "quit" || line == "exit" { + println!( + "{}", + format!("------------- Exiting Convex Demo -------------").blue() + ); + break; + } + + println!("{}", format!("Sending a message").yellow().bold()); + let result = client_ + .mutation( + "messages:send", + btreemap! { + "body".to_string() => line.into(), + "author".to_string() => sender.clone().into(), + }, + ) + .await?; + match result { + FunctionResult::Value(Value::Null) => { + println!("{}.", format!("Message sent").green().bold()); + }, + FunctionResult::Value(v) => { + println!( + "{}", + format!("Unexpected non-null result from messages:send {v:?}") + .red() + .bold() + ); + }, + FunctionResult::ErrorMessage(err) => { + println!("{}.", err.red().bold()); + }, + FunctionResult::ConvexError(err) => { + println!("{err:?}"); + }, + }; + } + + cancel_sender + .send(()) + .expect("Failed to send termination signal"); + handle.await?; + + Ok(()) +} + +fn readline() -> anyhow::Result { + let mut buffer = String::new(); + std::io::stdin().read_line(&mut buffer)?; + if buffer.ends_with('\n') { + buffer.pop(); + if buffer.ends_with('\r') { + buffer.pop(); + } + } + Ok(buffer) +} diff --git a/third_party/convex_rs/examples/quickstart/convex/tasks.ts b/third_party/convex_rs/examples/quickstart/convex/tasks.ts new file mode 100644 index 00000000..31159f01 --- /dev/null +++ b/third_party/convex_rs/examples/quickstart/convex/tasks.ts @@ -0,0 +1,7 @@ +import { query } from "./_generated/server"; + +export const get = query({ + handler: async ({ db }) => { + return await db.query("tasks").collect(); + }, +}); diff --git a/third_party/convex_rs/examples/quickstart/main.rs b/third_party/convex_rs/examples/quickstart/main.rs new file mode 100644 index 00000000..9e5949a0 --- /dev/null +++ b/third_party/convex_rs/examples/quickstart/main.rs @@ -0,0 +1,18 @@ +use std::{ + collections::BTreeMap, + env, +}; + +use convex::ConvexClient; + +#[tokio::main] +async fn main() { + dotenvy::from_filename(".env.local").ok(); + dotenvy::dotenv().ok(); + + let deployment_url = env::var("CONVEX_URL").unwrap(); + + let mut client = ConvexClient::new(&deployment_url).await.unwrap(); + let result = client.query("tasks:get", BTreeMap::new()).await.unwrap(); + println!("{result:#?}"); +} diff --git a/third_party/convex_rs/examples/quickstart/package-lock.json b/third_party/convex_rs/examples/quickstart/package-lock.json new file mode 100644 index 00000000..be5de5f4 --- /dev/null +++ b/third_party/convex_rs/examples/quickstart/package-lock.json @@ -0,0 +1,540 @@ +{ + "name": "convex-rust-quickstart", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "convex-rust-quickstart", + "dependencies": { + "convex": "^1.34.1" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.0.tgz", + "integrity": "sha512-KuZrd2hRjz01y5JK9mEBSD3Vj3mbCvemhT466rSuJYeE/hjuBrHfjjcjMdTm/sz7au+++sdbJZJmuBwQLuw68A==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.0.tgz", + "integrity": "sha512-j67aezrPNYWJEOHUNLPj9maeJte7uSMM6gMoxfPC9hOg8N02JuQi/T7ewumf4tNvJadFkvLZMlAq73b9uwdMyQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.0.tgz", + "integrity": "sha512-CC3vt4+1xZrs97/PKDkl0yN7w8edvU2vZvAFGD16n9F0Cvniy5qvzRXjfO1l94efczkkQE6g1x0i73Qf5uthOQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.0.tgz", + "integrity": "sha512-wurMkF1nmQajBO1+0CJmcN17U4BP6GqNSROP8t0X/Jiw2ltYGLHpEksp9MpoBqkrFR3kv2/te6Sha26k3+yZ9Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.0.tgz", + "integrity": "sha512-uJOQKYCcHhg07DL7i8MzjvS2LaP7W7Pn/7uA0B5S1EnqAirJtbyw4yC5jQ5qcFjHK9l6o/MX9QisBg12kNkdHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.0.tgz", + "integrity": "sha512-8mG6arH3yB/4ZXiEnXof5MK72dE6zM9cDvUcPtxhUZsDjESl9JipZYW60C3JGreKCEP+p8P/72r69m4AZGJd5g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.0.tgz", + "integrity": "sha512-9FHtyO988CwNMMOE3YIeci+UV+x5Zy8fI2qHNpsEtSF83YPBmE8UWmfYAQg6Ux7Gsmd4FejZqnEUZCMGaNQHQw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.0.tgz", + "integrity": "sha512-zCMeMXI4HS/tXvJz8vWGexpZj2YVtRAihHLk1imZj4efx1BQzN76YFeKqlDr3bUWI26wHwLWPd3rwh6pe4EV7g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.0.tgz", + "integrity": "sha512-t76XLQDpxgmq2cNXKTVEB7O7YMb42atj2Re2Haf45HkaUpjM2J0UuJZDuaGbPbamzZ7bawyGFUkodL+zcE+jvQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.0.tgz", + "integrity": "sha512-AS18v0V+vZiLJyi/4LphvBE+OIX682Pu7ZYNsdUHyUKSoRwdnOsMf6FDekwoAFKej14WAkOef3zAORJgAtXnlQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.0.tgz", + "integrity": "sha512-Mz1jxqm/kfgKkc/KLHC5qIujMvnnarD9ra1cEcrs7qshTUSksPihGrWHVG5+osAIQ68577Zpww7SGapmzSt4Nw==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.0.tgz", + "integrity": "sha512-QbEREjdJeIreIAbdG2hLU1yXm1uu+LTdzoq1KCo4G4pFOLlvIspBm36QrQOar9LFduavoWX2msNFAAAY9j4BDg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.0.tgz", + "integrity": "sha512-sJz3zRNe4tO2wxvDpH/HYJilb6+2YJxo/ZNbVdtFiKDufzWq4JmKAiHy9iGoLjAV7r/W32VgaHGkk35cUXlNOg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.0.tgz", + "integrity": "sha512-z9N10FBD0DCS2dmSABDBb5TLAyF1/ydVb+N4pi88T45efQ/w4ohr/F/QYCkxDPnkhkp6AIpIcQKQ8F0ANoA2JA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.0.tgz", + "integrity": "sha512-pQdyAIZ0BWIC5GyvVFn5awDiO14TkT/19FTmFcPdDec94KJ1uZcmFs21Fo8auMXzD4Tt+diXu1LW1gHus9fhFQ==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.0.tgz", + "integrity": "sha512-hPlRWR4eIDDEci953RI1BLZitgi5uqcsjKMxwYfmi4LcwyWo2IcRP+lThVnKjNtk90pLS8nKdroXYOqW+QQH+w==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.0.tgz", + "integrity": "sha512-1hBWx4OUJE2cab++aVZ7pObD6s+DK4mPGpemtnAORBvb5l/g5xFGk0vc0PjSkrDs0XaXj9yyob3d14XqvnQ4gw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.0.tgz", + "integrity": "sha512-6m0sfQfxfQfy1qRuecMkJlf1cIzTOgyaeXaiVaaki8/v+WB+U4hc6ik15ZW6TAllRlg/WuQXxWj1jx6C+dfy3w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.0.tgz", + "integrity": "sha512-xbbOdfn06FtcJ9d0ShxxvSn2iUsGd/lgPIO2V3VZIPDbEaIj1/3nBBe1AwuEZKXVXkMmpr6LUAgMkLD/4D2PPA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.0.tgz", + "integrity": "sha512-fWgqR8uNbCQ/GGv0yhzttj6sU/9Z5/Sv/VGU3F5OuXK6J6SlriONKrQ7tNlwBrJZXRYk5jUhuWvF7GYzGguBZQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.0.tgz", + "integrity": "sha512-aCwlRdSNMNxkGGqQajMUza6uXzR/U0dIl1QmLjPtRbLOx3Gy3otfFu/VjATy4yQzo9yFDGTxYDo1FfAD9oRD2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.0.tgz", + "integrity": "sha512-nyvsBccxNAsNYz2jVFYwEGuRRomqZ149A39SHWk4hV0jWxKM0hjBPm3AmdxcbHiFLbBSwG6SbpIcUbXjgyECfA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.0.tgz", + "integrity": "sha512-Q1KY1iJafM+UX6CFEL+F4HRTgygmEW568YMqDA5UV97AuZSm21b7SXIrRJDwXWPzr8MGr75fUZPV67FdtMHlHA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.0.tgz", + "integrity": "sha512-W1eyGNi6d+8kOmZIwi/EDjrL9nxQIQ0MiGqe/AWc6+IaHloxHSGoeRgDRKHFISThLmsewZ5nHFvGFWdBYlgKPg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.0.tgz", + "integrity": "sha512-30z1aKL9h22kQhilnYkORFYt+3wp7yZsHWus+wSKAJR8JtdfI76LJ4SBdMsCopTR3z/ORqVu5L1vtnHZWVj4cQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.0.tgz", + "integrity": "sha512-aIitBcjQeyOhMTImhLZmtxfdOcuNRpwlPNmlFKPcHQYPhEssw75Cl1TSXJXpMkzaua9FUetx/4OQKq7eJul5Cg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/convex": { + "version": "1.34.1", + "resolved": "https://registry.npmjs.org/convex/-/convex-1.34.1.tgz", + "integrity": "sha512-ooyFnZVVq0u6b5zt0Ptq8QB2ixhf/2vXe+PIcUtdtrs0lq/TwpkmmruHdqkFmWgMd6N+Tmfy8AGkz6QnZUYZBA==", + "license": "Apache-2.0", + "dependencies": { + "esbuild": "0.27.0", + "prettier": "^3.0.0", + "ws": "8.18.0" + }, + "bin": { + "convex": "bin/main.js" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=7.0.0" + }, + "peerDependencies": { + "@auth0/auth0-react": "^2.0.1", + "@clerk/clerk-react": "^4.12.8 || ^5.0.0", + "react": "^18.0.0 || ^19.0.0-0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@auth0/auth0-react": { + "optional": true + }, + "@clerk/clerk-react": { + "optional": true + }, + "react": { + "optional": true + } + } + }, + "node_modules/esbuild": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.0.tgz", + "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.27.0", + "@esbuild/android-arm": "0.27.0", + "@esbuild/android-arm64": "0.27.0", + "@esbuild/android-x64": "0.27.0", + "@esbuild/darwin-arm64": "0.27.0", + "@esbuild/darwin-x64": "0.27.0", + "@esbuild/freebsd-arm64": "0.27.0", + "@esbuild/freebsd-x64": "0.27.0", + "@esbuild/linux-arm": "0.27.0", + "@esbuild/linux-arm64": "0.27.0", + "@esbuild/linux-ia32": "0.27.0", + "@esbuild/linux-loong64": "0.27.0", + "@esbuild/linux-mips64el": "0.27.0", + "@esbuild/linux-ppc64": "0.27.0", + "@esbuild/linux-riscv64": "0.27.0", + "@esbuild/linux-s390x": "0.27.0", + "@esbuild/linux-x64": "0.27.0", + "@esbuild/netbsd-arm64": "0.27.0", + "@esbuild/netbsd-x64": "0.27.0", + "@esbuild/openbsd-arm64": "0.27.0", + "@esbuild/openbsd-x64": "0.27.0", + "@esbuild/openharmony-arm64": "0.27.0", + "@esbuild/sunos-x64": "0.27.0", + "@esbuild/win32-arm64": "0.27.0", + "@esbuild/win32-ia32": "0.27.0", + "@esbuild/win32-x64": "0.27.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/third_party/convex_rs/examples/quickstart/package.json b/third_party/convex_rs/examples/quickstart/package.json new file mode 100644 index 00000000..4015a3c7 --- /dev/null +++ b/third_party/convex_rs/examples/quickstart/package.json @@ -0,0 +1,7 @@ +{ + "name": "convex-rust-quickstart", + "private": true, + "dependencies": { + "convex": "^1.34.1" + } +} diff --git a/third_party/convex_rs/examples/quickstart/sampleData.jsonl b/third_party/convex_rs/examples/quickstart/sampleData.jsonl new file mode 100644 index 00000000..737408ee --- /dev/null +++ b/third_party/convex_rs/examples/quickstart/sampleData.jsonl @@ -0,0 +1,3 @@ +{"text": "Buy groceries", "isCompleted": true} +{"text": "Go for a swim", "isCompleted": true} +{"text": "Integrate Convex", "isCompleted": false} \ No newline at end of file diff --git a/third_party/convex_rs/rust-toolchain b/third_party/convex_rs/rust-toolchain new file mode 100644 index 00000000..2a8b1190 --- /dev/null +++ b/third_party/convex_rs/rust-toolchain @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly-2026-02-18" diff --git a/third_party/convex_rs/rustfmt.toml b/third_party/convex_rs/rustfmt.toml new file mode 100644 index 00000000..753cfef0 --- /dev/null +++ b/third_party/convex_rs/rustfmt.toml @@ -0,0 +1,16 @@ +use_field_init_shorthand = true +use_try_shorthand = true +match_block_trailing_comma = true + +# Nightly only options: +unstable_features = true +condense_wildcard_suffixes = true +format_strings = true +imports_granularity = "Crate" +reorder_impl_items = true +imports_layout = "Vertical" +group_imports = "StdExternalCrate" +wrap_comments = true +normalize_comments = false +error_on_line_overflow = true +style_edition = "2021" # in 2024 sort order is now case-insensitive, deferring the mass reformat diff --git a/third_party/convex_rs/src/base_client/mod.rs b/third_party/convex_rs/src/base_client/mod.rs new file mode 100644 index 00000000..5226fe3b --- /dev/null +++ b/third_party/convex_rs/src/base_client/mod.rs @@ -0,0 +1,1062 @@ +//! The synchronous state machine for Convex. It's +//! recommended to use the higher level [`ConvexClient`] unless you are building +//! a framework. +//! +//! See docs for [`BaseConvexClient`]. +use std::{ + cmp, + collections::{ + BTreeMap, + BTreeSet, + VecDeque, + }, + future::Future, + pin::Pin, +}; + +use convex_sync_types::{ + types::SerializedArgs, + AuthenticationToken, + CanonicalizedUdfPath, + ClientMessage, + IdentityVersion, + QueryId, + QuerySetModification, + QuerySetVersion, + SessionRequestSeqNumber, + StateModification, + StateVersion, + Timestamp, + UdfPath, +}; +use serde_json::json; +use tokio::sync::oneshot; + +#[cfg(doc)] +use crate::ConvexClient; +use crate::{ + convex_logs, + sync::{ + ReconnectProtocolReason, + ServerMessage, + }, + value::Value, + ConvexError, +}; + +mod request_manager; +use request_manager::{ + RequestId, + RequestManager, +}; +mod query_result; +pub use query_result::{ + FunctionResult, + QueryResults, +}; + +use self::request_manager::RequestType; + +/// A callback that fetches an auth token. The `bool` parameter indicates +/// whether a forced refresh is requested (e.g. on websocket reconnect). +pub type AuthTokenFetcher = Box< + dyn Fn(bool) -> Pin> + Send>> + + Send + + Sync, +>; + +#[derive(Clone, Eq, PartialEq, PartialOrd, Ord, Debug)] +struct QueryToken(String); + +#[derive(Clone, Debug)] +struct LocalQuery { + id: QueryId, + canonicalized_udf_path: CanonicalizedUdfPath, + args: BTreeMap, + num_subscribers: usize, // TODO: remove + /// A unique index value for each subscription to this query. + /// + /// Must be incremented each time a new subscription is added, and never + /// decremented. + subscription_index: usize, +} + +#[derive(Clone, Debug)] +struct Query { + result: FunctionResult, + _udf_path: CanonicalizedUdfPath, + _args: BTreeMap, +} + +/// An identifier for a single subscriber to a query. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord, Hash)] +#[cfg_attr(test, derive(proptest_derive::Arbitrary))] +pub struct SubscriberId(QueryId, usize); + +impl SubscriberId { + #[cfg(test)] + pub fn query_id(&self) -> QueryId { + self.0 + } +} + +fn serialize_path_and_args(udf_path: UdfPath, args: BTreeMap) -> QueryToken { + let json_path: String = udf_path.canonicalize().into(); + let json_args: serde_json::Value = Value::Array(vec![Value::Object(args)]).into(); + let json = json!({ + "udfPath": json_path, + "args": json_args, + }); + QueryToken(json.to_string()) +} + +#[derive(Default)] +struct LocalSyncState { + next_query_id: QueryId, + query_set_version: QuerySetVersion, + query_set: BTreeMap, + query_id_to_token: BTreeMap, + latest_results: QueryResults, + identity_version: IdentityVersion, + auth_fetcher: Option, + last_auth_token: AuthenticationToken, +} + +impl LocalSyncState { + fn subscribe( + &mut self, + udf_path: UdfPath, + args: BTreeMap, + ) -> (Option, SubscriberId) { + let canonicalized_udf_path = udf_path.clone().canonicalize(); + let query_token = serialize_path_and_args(udf_path.clone(), args.clone()); + + if let Some(existing_entry) = self.query_set.get_mut(&query_token) { + // This is a new subscription to an existing query. + existing_entry.num_subscribers += 1; + existing_entry.subscription_index += 1; + let query_id = existing_entry.id; + let subscription = SubscriberId(query_id, existing_entry.subscription_index); + let prev = self.latest_results.subscribers.insert(subscription); + assert!(prev.is_none(), "INTERNAL BUG: Subscriber ID already taken."); + return (None, subscription); + } + + let query_id = self.next_query_id; + self.next_query_id = QueryId::new(self.next_query_id.get_id() + 1); + let base_version = self.query_set_version; + self.query_set_version += 1; + let new_version = self.query_set_version; + + let add = QuerySetModification::Add(convex_sync_types::Query { + query_id, + udf_path, + args: SerializedArgs::from_args(vec![Value::Object(args.clone()).into()]) + .expect("Could not serialize query arguments"), + journal: None, + component_path: None, + }); + let message = ClientMessage::ModifyQuerySet { + base_version, + new_version, + modifications: vec![add], + }; + + let query = LocalQuery { + id: query_id, + canonicalized_udf_path, + args, + num_subscribers: 1, + subscription_index: 0, + }; + + self.query_set.insert(query_token.clone(), query); + self.query_id_to_token.insert(query_id, query_token.clone()); + let subscription = SubscriberId(query_id, 0); + let prev = self.latest_results.subscribers.insert(subscription); + assert!(prev.is_none(), "INTERNAL BUG: Subscriber ID already taken."); + (Some(message), subscription) + } + + fn remove_subscriber(&mut self, subscriber_id: SubscriberId) -> Option { + let query_id = self + .latest_results + .subscribers + .remove(&subscriber_id) + .expect("INTERNAL BUG: Dropped unknown Subscriber ID") + .0; + let query_token = match self.query_token(query_id) { + None => panic!("INTERNAL BUG: Unknown query id {query_id}"), + Some(t) => t, + }; + let local_query = match self.query_set.get_mut(&query_token) { + None => panic!("INTERNAL BUG: No query found for query token {query_token:?}",), + Some(q) => q, + }; + + // Update local state + if local_query.num_subscribers > 1 { + local_query.num_subscribers -= 1; + return None; + } + self.query_set.remove(&query_token); + self.query_id_to_token.remove(&query_id); + self.latest_results.results.remove(&query_id); + + let base_version = self.query_set_version; + self.query_set_version += 1; + let new_version = self.query_set_version; + + let remove = QuerySetModification::Remove { query_id }; + Some(ClientMessage::ModifyQuerySet { + base_version, + new_version, + modifications: vec![remove], + }) + } + + fn query_token(&self, query_id: QueryId) -> Option { + self.query_id_to_token.get(&query_id).cloned() + } + + fn query_args(&self, query_id: QueryId) -> Option> { + Some( + self.query_set + .get(&self.query_token(query_id)?)? + .args + .clone(), + ) + } + + fn query_path(&self, query_id: QueryId) -> Option { + Some( + self.query_set + .get(&self.query_token(query_id)?)? + .canonicalized_udf_path + .clone(), + ) + } + + fn authenticate(&mut self, token: AuthenticationToken) -> (ClientMessage, bool) { + let base_version = self.identity_version; + self.identity_version += 1; + let token_changed = token != self.last_auth_token; + self.last_auth_token = token.clone(); + ( + ClientMessage::Authenticate { + base_version, + token, + }, + token_changed, + ) + } + + async fn restart(&mut self) -> (Vec, bool) { + self.identity_version = 0; + let mut messages = Vec::new(); + let mut auth_token_changed = false; + + // If we have a fetcher, get a fresh token for the new connection. + if let Some(ref fetcher) = self.auth_fetcher { + match fetcher(true).await { + Ok(token) if token != AuthenticationToken::None => { + auth_token_changed = token != self.last_auth_token; + self.last_auth_token = token.clone(); + messages.push(ClientMessage::Authenticate { + base_version: 0, + token, + }); + self.identity_version += 1; + }, + Ok(_) => {}, + Err(e) => { + tracing::error!( + "Auth fetcher failed during reconnect: {e:?}. Skipping auth for this \ + reconnect attempt." + ); + }, + } + } + + let mut modifications = Vec::new(); + for local_query in self.query_set.values() { + let add = QuerySetModification::Add(convex_sync_types::Query { + query_id: local_query.id, + udf_path: local_query.canonicalized_udf_path.clone().into(), + args: SerializedArgs::from_args(vec![ + Value::Object(local_query.args.clone()).into() + ]) + .expect("Could not serialize query arguments"), + journal: None, + component_path: None, + }); + modifications.push(add) + } + self.query_set_version = 1; + + messages.push(ClientMessage::ModifyQuerySet { + base_version: 0, + new_version: 1, + modifications, + }); + + (messages, auth_token_changed) + } +} + +#[derive(Debug)] +struct RemoteQuerySet { + version: StateVersion, + remote_query_set: BTreeMap, +} + +impl RemoteQuerySet { + fn new() -> Self { + Self { + version: StateVersion::initial(), + remote_query_set: Default::default(), + } + } + + fn transition(&mut self, transition: ServerMessage) -> Result<(), ReconnectProtocolReason> { + let ServerMessage::Transition { + start_version, + end_version, + modifications, + client_clock_skew: _, + server_ts: _, + } = transition + else { + panic!("not transition"); + }; + if start_version != self.version { + tracing::error!( + "INTERNAL BUG: Protocol Error start_version {:?} is different from self.version \ + {:?}", + start_version, + self.version + ); + return Err("StartVersionMismatch".into()); + } + for modification in modifications { + match modification { + StateModification::QueryUpdated { + query_id, + value, + log_lines, + journal: _, + } => { + for log_line in log_lines.0 { + convex_logs!("{}", log_line); + } + self.remote_query_set + .insert(query_id, FunctionResult::Value(value)); + }, + StateModification::QueryFailed { + query_id, + error_message, + log_lines, + journal: _, + error_data, + } => { + for log_line in log_lines.0 { + convex_logs!("{}", log_line); + } + let function_result = match error_data { + Some(v) => FunctionResult::ConvexError(ConvexError { + message: error_message, + data: v, + }), + None => FunctionResult::ErrorMessage(error_message), + }; + self.remote_query_set.insert(query_id, function_result); + }, + StateModification::QueryRemoved { query_id } => { + self.remote_query_set.remove(&query_id); + }, + } + } + self.version = end_version; + Ok(()) + } +} + +#[derive(Default, Debug)] +struct OptimisticQueryResults { + query_results: BTreeMap, +} + +impl OptimisticQueryResults { + fn ingest_query_results_from_server( + &mut self, + server_query_results: BTreeMap, + _optimistic_updates_to_drop: BTreeSet, + ) -> BTreeMap { + // TODO: use optimistic_updates_to_drop + let old_query_results = self.query_results.clone(); + self.query_results = server_query_results; + let mut changed_queries = BTreeMap::new(); + for (query_id, query) in self.query_results.iter() { + let old_query = old_query_results.get(query_id); + if match old_query { + Some(old_query) => old_query.result != query.result, + None => true, + } { + let result = query.result.clone(); + changed_queries.insert(*query_id, result); + } + } + changed_queries + } + + fn query_result(&self, query_id: QueryId) -> Option { + self.query_results.get(&query_id).map(|q| q.result.clone()) + } +} + +/// The synchronous state machine for the `ConvexClient`. It's recommended to +/// use the higher level `ConvexClient` unless you are building a framework. +/// +/// This struct should be used instead of the `ConvexClient` when you want the +/// ability to build consistent client views. For example, in order to use your +/// own websocket manager or make a client compatible with another language +/// (e.g. Swift or Python). +/// +/// For the latter use case, we strongly recommend you to take a look at the +/// implementation of the `ConvexClient`. The recommended pattern to use an +/// [`BaseConvexClient`] is to create a background thread to manage actions on +/// queries/mutations and incoming websocket connections, and use that to +/// advance the BaseConvexClient's state. +/// +/// ## Managing Convex State +/// The main methods, [`subscribe`](Self::subscribe()), +/// [`unsubscribe`](Self::unsubscribe()), and +/// [`mutation`](Self::mutation()) directly correspond to its +/// equivalent for the external [ConvexClient]. +/// +/// The only different method is [`get_query`](Self::get_query()), which +/// returns the current value for a query given its query id. This method can be +/// used to synchronously request the current value, as opposed to a stream of +/// values in [`subscribe`](crate::ConvexClient::subscribe()). +/// +/// **Note: these methods have the side effect of +/// adding messages to be sent to the server, so you would need to flush all +/// outgoing messages by looping on +/// [`pop_next_message`](Self::pop_next_message()) after each call of the above +/// functions.** +/// +/// ## Watching for consistent updates to queries +/// To watch for consistent changes in query values, you can add the following +/// code to the background thread: +/// ```no_run +/// use convex::base_client::BaseConvexClient; +/// use convex::Value; +/// use convex_sync_types::ServerMessage; +/// +/// fn on_receive_server_message(mut base_client: BaseConvexClient, msg: ServerMessage) { +/// let res = base_client.receive_message(msg).expect("Base client error"); +/// if let Some(latest_result_map) = res { +/// for (subscriber_id, function_result) in latest_result_map.iter() { +/// // Notify components of the updated_value +/// } +/// } +/// } +/// ``` +/// +/// ## Managing Web Socket States +/// To manage websocket messages, use +/// [`receive_message`](Self::receive_message()) (for incoming messages from the +/// server) and [`pop_next_message`](Self::pop_next_message()) (for outgoing +/// messages to send to the server). **The [`BaseConvexClient`] does not +/// send these messages, so you will have to regularly monitor if there are +/// messages to be sent by calling +/// [`pop_next_message`](Self::pop_next_message()).** +/// +/// Additionally, when the websocket reconnects, you should call +/// [`resend_ongoing_queries_mutations`](Self::resend_ongoing_queries_mutations()) and loop on +/// [`pop_next_message`](Self::pop_next_message()) to resend requests to the +/// Server to resubscribe to queries and perform ongoing mutations. +/// +/// #### [`pop_next_message`](Self::pop_next_message()) should be called after the following methods: +/// - [`resend_ongoing_queries_mutations`](Self::resend_ongoing_queries_mutations()) +/// - [`subscribe`](Self::unsubscribe()) +/// - [`unsubscribe`](Self::unsubscribe()) +/// - [`mutation`](Self::unsubscribe()) +pub struct BaseConvexClient { + state: LocalSyncState, + remote_query_set: RemoteQuerySet, + optimistic_query_results: OptimisticQueryResults, + request_manager: RequestManager, + next_request_id: SessionRequestSeqNumber, + outgoing_message_queue: VecDeque, + max_observed_timestamp: Option, +} + +impl BaseConvexClient { + /// Construct a new [`BaseConvexClient`]. + pub fn new() -> Self { + let request_manager = RequestManager::new(); + let state = LocalSyncState::default(); + let remote_query_set = RemoteQuerySet::new(); + let optimistic_query_results: OptimisticQueryResults = Default::default(); + let next_request_id: SessionRequestSeqNumber = 0; + + BaseConvexClient { + request_manager, + state, + remote_query_set, + optimistic_query_results, + next_request_id, + outgoing_message_queue: VecDeque::new(), + max_observed_timestamp: None, + } + } + + /// Update state to be subscribed to a query and add subscription request to + /// the outgoing message queue. + /// + /// After calling this, it is highly recommended to loop on + /// [`pop_next_message`](Self::pop_next_message()) to flush websocket + /// messages to the server. + pub fn subscribe(&mut self, udf_path: UdfPath, args: BTreeMap) -> SubscriberId { + let (modification, subscription) = self.state.subscribe(udf_path, args); + if let Some(modification) = modification { + self.outgoing_message_queue.push_back(modification); + } + subscription + } + + /// Update state to be unsubscribed to a query and add unsubscription + /// request to the outgoing message queue. + /// + /// After calling this, it is highly recommended to loop on + /// [`pop_next_message`](Self::pop_next_message()) to flush websocket + /// messages to the server. + pub fn unsubscribe(&mut self, subscriber_id: SubscriberId) { + let unsubscribe_message = self.state.remove_subscriber(subscriber_id); + + if let Some(message) = unsubscribe_message { + self.outgoing_message_queue.push_back(message); + } + } + + /// Return the local value of a query. + pub fn get_query(&self, query_id: QueryId) -> Option { + self.local_query_result(query_id) + } + + /// Track mutation and add mutation request to the outgoing message queue. + /// + /// After calling this, it is highly recommended to loop on + /// [`pop_next_message`](Self::pop_next_message()) to flush websocket + /// messages to the server. + pub fn mutation( + &mut self, + udf_path: UdfPath, + args: BTreeMap, + ) -> oneshot::Receiver { + let request_id = self.next_request_id; + self.next_request_id = request_id + 1; + tracing::info!("Starting mutation {udf_path} with id {request_id}"); + let message = ClientMessage::Mutation { + request_id, + udf_path, + args: SerializedArgs::from_args(vec![Value::Object(args).into()]) + .expect("Failed to serialize arguments"), + component_path: None, + }; + + let result_receiver = self.request_manager.track_request( + &message, + RequestId::new(request_id), + RequestType::Mutation, + ); + self.outgoing_message_queue.push_back(message); + result_receiver + } + + /// Track action and add action request to the outgoing message queue. + /// + /// After calling this, it is highly recommended to loop on + /// [`pop_next_message`](Self::pop_next_message()) to flush websocket + /// messages to the server. + pub fn action( + &mut self, + udf_path: UdfPath, + args: BTreeMap, + ) -> oneshot::Receiver { + let request_id = self.next_request_id; + self.next_request_id = request_id + 1; + tracing::info!("Starting action {udf_path:?} with id {request_id:?}"); + let message = ClientMessage::Action { + request_id, + udf_path, + args: SerializedArgs::from_args(vec![Value::Object(args).into()]).unwrap(), + component_path: None, + }; + + let result_receiver = self.request_manager.track_request( + &message, + RequestId::new(request_id), + RequestType::Action, + ); + self.outgoing_message_queue.push_back(message); + result_receiver + } + + /// Store (or clear) an auth token fetcher callback and update auth state. + /// + /// When a fetcher is provided it is invoked immediately (with + /// `force_refresh=false`) and stored for future reconnects — on each + /// websocket reconnect the fetcher is called again with + /// `force_refresh=true`. + /// + /// When `None` is passed the stored fetcher is cleared and auth is unset. + pub async fn set_auth_fetcher(&mut self, fetcher: Option) -> bool { + let mut auth_token_changed = false; + match fetcher { + Some(fetcher) => { + match fetcher(false).await { + Ok(token) => { + let (message, changed) = self.state.authenticate(token); + auth_token_changed = changed; + self.outgoing_message_queue.push_back(message); + }, + Err(e) => { + tracing::error!("Auth token fetcher failed: {e:?}"); + }, + } + self.state.auth_fetcher = Some(fetcher); + }, + None => { + self.state.auth_fetcher = None; + let (message, changed) = self.state.authenticate(AuthenticationToken::None); + auth_token_changed = changed; + self.outgoing_message_queue.push_back(message); + }, + } + auth_token_changed + } + + /// Pop the next message from the outgoing message queue. + /// + /// Note that this does not *send* the message because the Internal client + /// has no awareness of websockets. After popping the next message, it is + /// the caller's responsibility to actually send it. + pub fn pop_next_message(&mut self) -> Option { + self.outgoing_message_queue.pop_front() + } + + fn observe_timestamp(&mut self, ts: Timestamp) { + if let Some(max_observed_timestamp) = self.max_observed_timestamp { + self.max_observed_timestamp = Some(cmp::max(ts, max_observed_timestamp)); + } else { + self.max_observed_timestamp = Some(ts); + } + } + + /// Returns the maximum timestamp observed by the client. + pub fn max_observed_timestamp(&self) -> Option { + self.max_observed_timestamp + } + + /// Given a message from a Server, update the base state accordingly. + pub fn receive_message( + &mut self, + message: ServerMessage, + ) -> Result, ReconnectProtocolReason> { + match message { + ServerMessage::Transition { end_version, .. } => { + self.observe_timestamp(end_version.ts); + self.remote_query_set.transition(message)?; + let completed_requests = self + .request_manager + .remove_and_notify_completed(end_version.ts); + let changed_query_ids = self.on_query_result_changes(completed_requests)?; + for (id, result) in changed_query_ids { + self.state.latest_results.results.insert(id, result); + } + return Ok(Some(self.state.latest_results.clone())); + }, + ServerMessage::MutationResponse { + request_id, + result, + ts, + log_lines, + } => { + for log_line in log_lines.0 { + convex_logs!("{}", log_line); + } + + if let Some(ts) = ts { + self.observe_timestamp(ts); + } + let request_id = RequestId::new(request_id); + self.request_manager.update_request( + &request_id, + RequestType::Mutation, + result.into(), + ts, + )?; + }, + ServerMessage::AuthError { + error_message, + base_version, + .. + } => { + tracing::error!( + "AuthError: {error_message} for identity version {base_version:?}. Restarting \ + protocol." + ); + return Err(format!( + "AuthError: {error_message} for identity version {base_version:?}" + )); + }, + ServerMessage::FatalError { error_message } => { + tracing::error!("FatalError: {error_message}. Restarting protocol."); + return Err(format!("FatalError: {error_message}")); + }, + ServerMessage::ActionResponse { + request_id, + result, + log_lines, + } => { + for log_line in log_lines.0 { + convex_logs!("{}", log_line); + } + let request_id = RequestId::new(request_id); + self.request_manager.update_request( + &request_id, + RequestType::Action, + result.into(), + None, + )?; + }, + ServerMessage::Ping => { + // Do nothing + }, + ServerMessage::TransitionChunk { .. } => { + // The Rust client should never receive TransitionChunk messages + // as this feature is only enabled for npm clients + return Err("Unexpected TransitionChunk message received".to_string()); + }, + } + Ok(None) + } + + /// Grab a snapshot of the latest query results to all subscribed queries. + pub fn latest_results(&self) -> &QueryResults { + &self.state.latest_results + } + + /// Resend all subscribed queries and ongoing mutations. Should be used once + /// the websocket closes and reconnects. + pub async fn resend_ongoing_queries_mutations(&mut self) -> bool { + // Clear any stale messages from the queue. During reconnection + // retries, messages can accumulate from previous failed attempts + // or from subscription changes made while disconnected. Since + // restart() rebuilds the full query set and resets version + // numbers, any pre-existing messages would have stale versions + // that conflict with the fresh restart messages. + self.outgoing_message_queue.clear(); + + let (state_restart_messages, auth_token_changed) = self.state.restart().await; + let mut ongoing_mutation_messages = self.request_manager.restart(); + + self.remote_query_set = RemoteQuerySet::new(); + for state_restart_message in state_restart_messages { + self.outgoing_message_queue.push_back(state_restart_message); + } + self.outgoing_message_queue + .append(&mut ongoing_mutation_messages); + auth_token_changed + } + + fn on_query_result_changes( + &mut self, + completed_requests: BTreeSet, + ) -> Result, ReconnectProtocolReason> { + let remote_query_results = &self.remote_query_set.remote_query_set; + let mut query_id_to_value = BTreeMap::new(); + for (query_id, result) in remote_query_results.iter() { + let Some(_udf_path) = self.state.query_path(*query_id) else { + // It's possible that we've already unsubscribed to this query but + // the server hasn't learned about that yet. If so, ignore this one. + continue; + }; + let _args = self + .state + .query_args(*query_id) + .expect("INTERNAL BUG: Query args exist, but not query path."); + query_id_to_value.insert( + *query_id, + Query { + result: result.clone(), + _udf_path, + _args, + }, + ); + } + Ok(self + .optimistic_query_results + .ingest_query_results_from_server(query_id_to_value, completed_requests)) + } + + fn local_query_result(&self, query_id: QueryId) -> Option { + self.optimistic_query_results.query_result(query_id) + } +} + +/// Macro used for piping UDF logs to a custom formatter that exposes +/// just the log content, without any additional Rust metadata. +#[macro_export] +macro_rules! convex_logs { + (target: $target:expr, $($arg:tt)+) => { + tracing::event!(target: "convex_logs", tracing::Level::DEBUG, $($arg)+); + // Additional custom behavior can be added here + }; + ($($arg:tt)+) => { + tracing::event!(target: "convex_logs", tracing::Level::DEBUG, $($arg)+); + // Additional custom behavior can be added here + }; +} + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use convex_sync_types::{ + AuthenticationToken, + ClientMessage, + LogLinesMessage, + QuerySetVersion, + UdfPath, + }; + use maplit::btreemap; + + use super::*; + + /// Simulates the server-side version tracking from + /// `sync::state::SyncState::modify_query_set`. Returns Err with the + /// same message the server produces when versions don't match. + fn simulate_server_version_check(messages: &[ClientMessage]) -> Result<(), String> { + let mut query_set_version: QuerySetVersion = 0; + for msg in messages { + if let ClientMessage::ModifyQuerySet { + base_version, + new_version, + .. + } = msg + { + if *base_version != query_set_version { + return Err(format!( + "Base version {base_version} passed up doesn't match the current version \ + {query_set_version}" + )); + } + query_set_version = *new_version; + } + } + Ok(()) + } + + /// Reproduces the bug where repeated reconnection attempts accumulate + /// stale messages in the outgoing queue, causing the server to reject + /// messages with "Base version 0 passed up doesn't match the current + /// version 1". + /// + /// In the real client, this happens when `communicate()` is interrupted + /// by a `ProtocolResponse::Failure` mid-drain (e.g. the WebSocket + /// connection attempt fails). The first message is popped and sent to + /// the WebSocket worker channel, but remaining messages stay in the + /// queue. When `resend_ongoing_queries_mutations()` appends fresh + /// restart messages, the stale leftovers cause version conflicts. + #[tokio::test] + async fn test_reconnect_does_not_send_duplicate_version_messages() { + let mut client = BaseConvexClient::new(); + + // Authenticated client with one active subscription. + client + .set_auth_fetcher(Some(Box::new(|_force_refetch| { + Box::pin(async { Ok(AuthenticationToken::User("test-token".into())) }) + }))) + .await; + let udf = UdfPath::from_str("some:query").unwrap(); + client.subscribe(udf, btreemap! {}); + + // Drain initial messages (successfully sent while connected). + while client.pop_next_message().is_some() {} + + // --- Connection drops, first reconnect attempt --- + assert!(!client.resend_ongoing_queries_mutations().await); + + // Simulate partial drain: the first message (Authenticate) was + // popped and handed to the WebSocket layer, but the connection + // failed before the second message (ModifyQuerySet) could be sent. + let _ = client.pop_next_message(); + + // --- Connection still down, second reconnect attempt --- + assert!(!client.resend_ongoing_queries_mutations().await); + + // Connection finally succeeds — all queued messages are flushed. + let mut messages = vec![]; + while let Some(msg) = client.pop_next_message() { + messages.push(msg); + } + + // The server tracks query set versions sequentially and rejects + // any message whose base_version doesn't match its current state + // (sync::state::SyncState::modify_query_set). Without the fix, + // the stale ModifyQuerySet{base_version:0} from the first attempt + // is still in the queue, followed by the second attempt's + // ModifyQuerySet{base_version:0}. + simulate_server_version_check(&messages) + .expect("Server would reject these messages with a FatalError"); + } + + #[tokio::test] + async fn test_reconnect_path_requests_refreshed_token() { + let mut client = BaseConvexClient::new(); + + // Authenticated client with one active subscription. + client + .set_auth_fetcher(Some(Box::new(|force_refetch| { + Box::pin(async move { + if force_refetch { + // A fake refreshed token. + Ok(AuthenticationToken::User("refetched-token".into())) + } else { + Ok(AuthenticationToken::User("original-token".into())) + } + }) + }))) + .await; + let udf = UdfPath::from_str("some:query").unwrap(); + client.subscribe(udf, btreemap! {}); + + // Drain initial messages (successfully sent while connected). + while client.pop_next_message().is_some() {} + + // --- Connection drops, reconnect attempt --- + assert!(client.resend_ongoing_queries_mutations().await); + + // A fresh authentication attempt should have been initiated, with a new token. + assert_eq!( + client + .pop_next_message() + .expect("Expected an authentication message."), + ClientMessage::Authenticate { + base_version: 0, + token: AuthenticationToken::User("refetched-token".into()), + } + ); + } + + fn drain_add_message(client: &mut BaseConvexClient) -> QueryId { + match client.pop_next_message() { + Some(ClientMessage::ModifyQuerySet { modifications, .. }) => { + let [QuerySetModification::Add(query)] = modifications.as_slice() else { + panic!("expected a single add modification, got {modifications:?}"); + }; + query.query_id + }, + other => panic!("expected add query message, got {other:?}"), + } + } + + fn drain_remove_message(client: &mut BaseConvexClient) -> QueryId { + match client.pop_next_message() { + Some(ClientMessage::ModifyQuerySet { modifications, .. }) => { + let [QuerySetModification::Remove { query_id }] = modifications.as_slice() else { + panic!("expected a single remove modification, got {modifications:?}"); + }; + *query_id + }, + other => panic!("expected remove query message, got {other:?}"), + } + } + + fn apply_query_update( + client: &mut BaseConvexClient, + version: &mut StateVersion, + query_id: QueryId, + value: Value, + ) { + let end_version = StateVersion { + ts: version.ts.succ().expect("timestamp overflow in test"), + ..*version + }; + let transition = ServerMessage::Transition { + start_version: *version, + end_version, + modifications: vec![StateModification::QueryUpdated { + query_id, + value, + log_lines: LogLinesMessage(vec![]), + journal: None, + }], + client_clock_skew: None, + server_ts: None, + }; + + let latest_results = client + .receive_message(transition) + .expect("transition should be accepted"); + assert!( + latest_results.is_some(), + "query update should publish results" + ); + *version = end_version; + } + + #[test] + fn test_final_unsubscribe_removes_cached_query_result() { + let mut client = BaseConvexClient::new(); + let mut version = StateVersion::initial(); + // Add a subscriber. + let subscriber_id = client.subscribe("getValue1".parse().unwrap(), BTreeMap::new()); + let query_id = drain_add_message(&mut client); + assert!(client.pop_next_message().is_none()); + + apply_query_update(&mut client, &mut version, query_id, 10.into()); + assert!(client.state.latest_results.results.contains_key(&query_id)); + assert_eq!( + client.latest_results().get(&subscriber_id), + Some(&FunctionResult::Value(10.into())) + ); + + client.unsubscribe(subscriber_id); + + assert_eq!(drain_remove_message(&mut client), query_id); + assert!(client.pop_next_message().is_none()); + + // The latest_results are gone since there are no more subscribers. + assert!(client.state.latest_results.subscribers.is_empty()); + assert!(!client.state.latest_results.results.contains_key(&query_id)); + } + + #[test] + fn test_cached_query_result_persists_while_subscribers_exist() { + let mut client = BaseConvexClient::new(); + let mut version = StateVersion::initial(); + // Add two subscribers. + let subscriber_a = client.subscribe("getValue1".parse().unwrap(), BTreeMap::new()); + let query_id = drain_add_message(&mut client); + let subscriber_b = client.subscribe("getValue1".parse().unwrap(), BTreeMap::new()); + assert!(client.pop_next_message().is_none()); + + apply_query_update(&mut client, &mut version, query_id, 10.into()); + + // The first subscriber drops. + client.unsubscribe(subscriber_a); + + assert!(client.pop_next_message().is_none()); + + // The latest_results persist since a subscriber still exists. + assert!(client.state.latest_results.results.contains_key(&query_id)); + assert_eq!( + client.latest_results().get(&subscriber_b), + Some(&FunctionResult::Value(10.into())) + ); + } +} diff --git a/third_party/convex_rs/src/base_client/query_result.rs b/third_party/convex_rs/src/base_client/query_result.rs new file mode 100644 index 00000000..4f870150 --- /dev/null +++ b/third_party/convex_rs/src/base_client/query_result.rs @@ -0,0 +1,159 @@ +use convex_sync_types::{ + types::ErrorPayload, + QueryId, +}; +use imbl::{ + OrdMap, + OrdSet, +}; + +use super::SubscriberId; +use crate::{ + ConvexError, + Value, +}; + +/// Result of a Convex function (query/mutation/action). +/// +/// The function returns a Convex value or an error message string. +#[derive(Clone, Eq, PartialEq)] +pub enum FunctionResult { + /// The Convex value returned on a successful run of a Convex function + Value(Value), + /// The error message of a Convex function run that does not complete + /// successfully. + ErrorMessage(String), + /// The error payload of a Convex function run that doesn't complete + /// successfully, with an application-level error. + ConvexError(ConvexError), +} + +impl From>> for FunctionResult { + fn from(result: Result>) -> Self { + match result { + Ok(value) => FunctionResult::Value(value), + Err(ErrorPayload::ErrorData { message, data }) => { + FunctionResult::ConvexError(ConvexError { message, data }) + }, + Err(ErrorPayload::Message(message)) => FunctionResult::ErrorMessage(message), + } + } +} + +impl From for Result> { + fn from(result: FunctionResult) -> Self { + match result { + FunctionResult::Value(value) => Ok(value), + FunctionResult::ErrorMessage(error) => Err(ErrorPayload::Message(error)), + FunctionResult::ConvexError(error) => Err(ErrorPayload::ErrorData { + message: error.message, + data: error.data, + }), + } + } +} + +impl std::fmt::Debug for FunctionResult { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + FunctionResult::Value(value) => f.debug_tuple("Value").field(value).finish(), + FunctionResult::ErrorMessage(error) => write!(f, "{error}"), + FunctionResult::ConvexError(error) => { + f.debug_tuple("ConvexError").field(error).finish() + }, + } + } +} + +/// A mapping from [`SubscriberId`] to its current result [`FunctionResult`] +/// for each actively subscribed query. +#[derive(Clone, Default, Debug)] +pub struct QueryResults { + pub(super) results: OrdMap, + pub(super) subscribers: OrdSet, +} + +impl QueryResults { + /// Get the [`FunctionResult`] for the given [`SubscriberId`] + pub fn get(&self, subscriber_id: &SubscriberId) -> Option<&FunctionResult> { + if !self.subscribers.contains(subscriber_id) { + return None; + }; + self.results.get(&subscriber_id.0) + } + + /// Get the size of the map. + pub fn len(&self) -> usize { + self.subscribers.len() + } + + /// Test whether the map is empty. + pub fn is_empty(&self) -> bool { + self.subscribers.is_empty() + } + + /// Get an iterator over the subscriber_id/query_result pairs of the map. + pub fn iter(&self) -> impl Iterator)> { + self.subscribers.iter().map(|s| (s, self.results.get(&s.0))) + } +} + +#[cfg(test)] +mod tests { + use convex_sync_types::QueryId; + use imbl::{ + ordmap, + ordset, + }; + + use crate::{ + base_client::SubscriberId, + FunctionResult, + QueryResults, + Value, + }; + + #[test] + fn test_query_results() { + let q = QueryId::new; + let s = SubscriberId; + + let qr = QueryResults { + results: ordmap! { + q(0) => FunctionResult::Value(Value::Null), + q(1) => FunctionResult::Value(Value::Int64(5)) + }, + subscribers: ordset! { + s(q(0), 0), + s(q(0), 1), + s(q(1), 0), + s(q(2), 0) + }, + }; + assert_eq!( + qr.get(&s(q(0), 0)), + Some(&FunctionResult::Value(Value::Null)) + ); + assert_eq!( + qr.get(&s(q(0), 1)), + Some(&FunctionResult::Value(Value::Null)) + ); + assert_eq!( + qr.get(&s(q(1), 0)), + Some(&FunctionResult::Value(Value::Int64(5))) + ); + assert_eq!(qr.get(&s(q(2), 0)), None,); + assert_eq!(qr.len(), 4); + assert!(!qr.is_empty()); + let v: Vec<_> = qr.iter().collect(); + assert_eq!( + v, + vec![ + (&s(q(0), 0), Some(&FunctionResult::Value(Value::Null))), + (&s(q(0), 1), Some(&FunctionResult::Value(Value::Null))), + (&s(q(1), 0), Some(&FunctionResult::Value(Value::Int64(5)))), + (&s(q(2), 0), None), + ], + ); + } +} diff --git a/third_party/convex_rs/src/base_client/request_manager.rs b/third_party/convex_rs/src/base_client/request_manager.rs new file mode 100644 index 00000000..8c461354 --- /dev/null +++ b/third_party/convex_rs/src/base_client/request_manager.rs @@ -0,0 +1,170 @@ +use std::{ + cmp::Reverse, + collections::{ + BTreeMap, + BTreeSet, + VecDeque, + }, +}; + +use convex_sync_types::{ + ClientMessage, + Timestamp, +}; +use tokio::sync::oneshot; + +use crate::{ + sync::ReconnectProtocolReason, + FunctionResult, +}; + +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq, Debug)] +pub struct RequestId(u32); +impl RequestId { + pub fn new(id: u32) -> Self { + RequestId(id) + } +} + +#[derive(Copy, Clone, PartialEq, PartialOrd, Ord, Eq)] +pub enum RequestType { + Mutation, + Action, +} + +#[derive(Clone, PartialEq, PartialOrd, Ord, Eq)] +pub enum RequestStatus { + Requested, + Completed, +} + +#[derive(Clone, PartialEq, Eq)] +pub struct Request { + pub id: RequestId, + pub typ: RequestType, + pub status: RequestStatus, + pub ts: Option, + pub value: Option, + pub message: ClientMessage, +} + +impl Request { + pub fn new(id: RequestId, typ: RequestType, message: ClientMessage) -> Self { + Request { + id, + typ, + status: RequestStatus::Requested, + ts: None, + value: None, + message, + } + } + + pub fn update_value(&mut self, value: FunctionResult) { + self.value = Some(value); + } + + pub fn update_timestamp(&mut self, ts: Option) { + self.ts = ts; + } +} + +pub struct RequestManager { + ongoing_requests: BTreeMap)>, +} + +impl RequestManager { + pub fn new() -> Self { + RequestManager { + ongoing_requests: BTreeMap::new(), + } + } + + pub fn update_request( + &mut self, + request_id: &RequestId, + request_type: RequestType, + value: FunctionResult, + ts: Option, + ) -> Result<(), ReconnectProtocolReason> { + let Some((request, _)) = self.ongoing_requests.get_mut(request_id) else { + return Err("Invalid request id from server".to_string()); + }; + if request.typ != request_type { + return Err("Mismatched request type from server".to_string()); + }; + let errored = matches!(value, FunctionResult::ErrorMessage(_)); + request.update_value(value); + request.update_timestamp(ts); + request.status = RequestStatus::Completed; + + // Actions and errored mutations are ok to complete immediately + if request_type == RequestType::Action || errored { + self._remove_and_notify_completed(request_id); + } + Ok(()) + } + + pub fn remove_and_notify_completed(&mut self, ts: Timestamp) -> BTreeSet { + let mut completed_requests = BTreeSet::new(); + for (id, (request, _)) in self.ongoing_requests.iter() { + let mut is_completed = false; + if request.status == RequestStatus::Completed { + is_completed = true; + } + if let Some(request_ts) = request.ts { + if request_ts <= ts { + is_completed = true; + } + } + if is_completed { + completed_requests.insert(*id); + } + } + for id in completed_requests.iter() { + self._remove_and_notify_completed(id); + } + completed_requests + } + + fn _remove_and_notify_completed(&mut self, request_id: &RequestId) { + let (request, sender) = self + .ongoing_requests + .remove(request_id) + .expect("INTERNAL BUG: request_id must be present"); + if let Err(value) = sender.send( + request + .value + .expect("INTERNAL BUG: Value missing on completed request"), + ) { + tracing::info!( + "Request {request_id:?} completed with result {value:?}, but result receiver was \ + dropped" + ); + } + } + + pub fn track_request( + &mut self, + message: &ClientMessage, + request_id: RequestId, + request_type: RequestType, + ) -> oneshot::Receiver { + let (tx, rx) = oneshot::channel(); + let request = Request::new(request_id, request_type, message.clone()); + self.ongoing_requests.insert(request_id, (request, tx)); + rx + } + + pub fn restart(&self) -> VecDeque { + // Sort ongoing requests by timestamp + let mut ordered_requests = Vec::from_iter(self.ongoing_requests.values()); + ordered_requests.sort_by_key(|(req, _)| Reverse(req.ts)); + + let mut messages = VecDeque::new(); + for (request, _) in ordered_requests { + messages.push_back(request.message.clone()); + } + messages + } +} diff --git a/third_party/convex_rs/src/client/mod.rs b/third_party/convex_rs/src/client/mod.rs new file mode 100644 index 00000000..012554e1 --- /dev/null +++ b/third_party/convex_rs/src/client/mod.rs @@ -0,0 +1,1153 @@ +use std::{ + collections::BTreeMap, + convert::Infallible, + future::Future, + pin::Pin, + sync::Arc, +}; + +use convex_sync_types::{ + AuthenticationToken, + UdfPath, + UserIdentityAttributes, +}; +#[cfg(doc)] +use futures::Stream; +use futures::StreamExt; +use tokio::{ + sync::{ + broadcast, + mpsc, + oneshot, + }, + task::JoinHandle, +}; +use tokio_stream::wrappers::BroadcastStream; +use url::Url; + +pub use crate::base_client::AuthTokenFetcher; +#[cfg(doc)] +use crate::SubscriberId; +use crate::{ + base_client::{ + BaseConvexClient, + QueryResults, + }, + client::{ + subscription::{ + QuerySetSubscription, + QuerySubscription, + }, + worker::{ + worker, + ActionRequest, + ClientRequest, + MutationRequest, + SubscribeRequest, + }, + }, + sync::{ + web_socket_manager::WebSocketManager, + SyncProtocol, + WebSocketState, + }, + value::Value, + FunctionResult, +}; + +pub mod subscription; +mod worker; + +const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION"); + +/// An asynchronous client to interact with a specific project to perform +/// mutations and manage query subscriptions using [`tokio`]. +/// +/// The Convex client requires a deployment url, +/// which can be found in the [dashboard](https://dashboard.convex.dev/) settings tab. +/// +/// ```no_run +/// use convex::ConvexClient; +/// use futures::StreamExt; +/// +/// #[tokio::main] +/// async fn main() -> anyhow::Result<()> { +/// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; +/// let mut sub = client.subscribe("listMessages", maplit::btreemap!{}).await?; +/// while let Some(result) = sub.next().await { +/// println!("{result:?}"); +/// } +/// Ok(()) +/// } +/// ``` +/// +/// The [`ConvexClient`] internally holds a connection and a [`tokio`] +/// background task to manage it. It is advised that you create one and +/// **reuse** it. You can safely clone with [`ConvexClient::clone()`] to share +/// the connection and outstanding subscriptions. +/// +/// ## Examples +/// For example code, please refer to the examples directory. +pub struct ConvexClient { + listen_handle: Option>>, + request_sender: mpsc::UnboundedSender, + watch_receiver: broadcast::Receiver, +} + +/// Clone the [`ConvexClient`], sharing the connection and outstanding +/// subscriptions. +impl Clone for ConvexClient { + fn clone(&self) -> Self { + Self { + listen_handle: self.listen_handle.clone(), + request_sender: self.request_sender.clone(), + watch_receiver: self.watch_receiver.resubscribe(), + } + } +} + +/// Drop the [`ConvexClient`]. When the final reference to the [`ConvexClient`] +/// is dropped, the connection is cleaned up. +impl Drop for ConvexClient { + fn drop(&mut self) { + if let Ok(j_handle) = Arc::try_unwrap( + self.listen_handle + .take() + .expect("INTERNAL BUG: listen handle should never be none"), + ) { + j_handle.abort() + } + } +} + +impl ConvexClient { + /// Constructs a new client for communicating with `deployment_url`. + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn new(deployment_url: &str) -> anyhow::Result { + ConvexClient::new_from_builder(ConvexClientBuilder::new(deployment_url)).await + } + + #[doc(hidden)] + pub async fn new_from_builder(builder: ConvexClientBuilder) -> anyhow::Result { + let client_id = builder + .client_id + .unwrap_or_else(|| format!("rust-{}", VERSION.unwrap_or("unknown"))); + let ws_url = deployment_to_ws_url(builder.deployment_url.as_str().try_into()?)?; + + // Channels for the `listen` background thread + let (response_sender, response_receiver) = mpsc::channel(1); + let (request_sender, request_receiver) = mpsc::unbounded_channel(); + + // Listener for when each transaction completes + let (watch_sender, watch_receiver) = broadcast::channel(1); + + let base_client = BaseConvexClient::new(); + + let protocol = WebSocketManager::open( + ws_url, + response_sender, + builder.on_state_change, + client_id.as_str(), + ) + .await?; + + let listen_handle = tokio::spawn(worker( + response_receiver, + request_receiver, + watch_sender, + base_client, + protocol, + )); + let client = ConvexClient { + listen_handle: Some(Arc::new(listen_handle)), + request_sender, + watch_receiver, + }; + Ok(client) + } + + /// Subscribe to the results of query `name` called with `args`. + /// + /// Returns a [`QuerySubscription`] which implements [`Stream`]< + /// [`FunctionResult`]>. A new value appears on the stream each + /// time the query function produces a new result. + /// + /// The subscription is automatically unsubscribed when it is dropped. + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # use futures::StreamExt; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// let mut sub = client.subscribe("listMessages", maplit::btreemap!{}).await?; + /// while let Some(result) = sub.next().await { + /// println!("{result:?}"); + /// } + /// # Ok(()) + /// # } + pub async fn subscribe( + &mut self, + name: &str, + args: BTreeMap, + ) -> anyhow::Result { + let (tx, rx) = oneshot::channel(); + + let udf_path = name.parse()?; + let request = SubscribeRequest { udf_path, args }; + + self.request_sender.send(ClientRequest::Subscribe( + request, + tx, + self.request_sender.clone(), + ))?; + + let res = rx.await?; + Ok(res) + } + + /// Make a oneshot request to a query `name` with `args`. + /// + /// Returns a [`FunctionResult`] representing the result of the query. + /// + /// This method is syntactic sugar for waiting for a single result on + /// a subscription. + /// It is equivalent to `client.subscribe(name, + /// args).await?.next().unwrap()` + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # use futures::StreamExt; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// let result = client.query("listMessages", maplit::btreemap!{}).await?; + /// println!("{result:?}"); + /// # Ok(()) + /// # } + pub async fn query( + &mut self, + name: &str, + args: BTreeMap, + ) -> anyhow::Result { + Ok(self + .subscribe(name, args) + .await? + .next() + .await + .expect("INTERNAL BUG: Convex Client dropped prematurely.")) + } + + /// Perform a mutation `name` with `args` and return a future + /// containing the return value of the mutation once it completes. + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # use futures::StreamExt; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// let result = client.mutation("sendMessage", maplit::btreemap!{ + /// "body".into() => "Let it be.".into(), + /// "author".into() => "The Beatles".into(), + /// }).await?; + /// println!("{result:?}"); + /// # Ok(()) + /// # } + pub async fn mutation( + &mut self, + name: &str, + args: BTreeMap, + ) -> anyhow::Result { + let (tx, rx) = oneshot::channel(); + + let udf_path: UdfPath = name.parse()?; + let request = MutationRequest { udf_path, args }; + + self.request_sender + .send(ClientRequest::Mutation(request, tx))?; + + let res = rx.await?; + Ok(res.await?) + } + + /// Perform an action `name` with `args` and return a future + /// containing the return value of the action once it completes. + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # use futures::StreamExt; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// let result = client.action("sendGif", maplit::btreemap!{ + /// "body".into() => "Tatooine Sunrise.".into(), + /// "author".into() => "Luke Skywalker".into(), + /// }).await?; + /// println!("{result:?}"); + /// # Ok(()) + /// # } + pub async fn action( + &mut self, + name: &str, + args: BTreeMap, + ) -> anyhow::Result { + let (tx, rx) = oneshot::channel(); + + let udf_path: UdfPath = name.parse()?; + let request = ActionRequest { udf_path, args }; + + self.request_sender + .send(ClientRequest::Action(request, tx))?; + + let res = rx.await?; + Ok(res.await?) + } + + /// Get a consistent view of the results of multiple queries (query set). + /// + /// Returns a [`QuerySetSubscription`] which + /// implements [`Stream`]<[`QueryResults`]>. + /// Each item in the stream contains a consistent view + /// of the results of all the queries in the query set. + /// + /// Queries can be added to the query set via [`ConvexClient::subscribe`]. + /// Queries can be removed from the query set via dropping the + /// [`QuerySubscription`] token returned by [`ConvexClient::subscribe`]. + /// + /// + /// [`QueryResults`] is a copy-on-write mapping from [`SubscriberId`] to + /// its latest result [`Value`]. + /// + /// ```no_run + /// # use convex::ConvexClient; + /// # use futures::StreamExt; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; + /// let mut watch = client.watch_all(); + /// let sub1 = client.subscribe("listMessages", maplit::btreemap!{ + /// "channel".into() => 1.into(), + /// }).await?; + /// let sub2 = client.subscribe("listMessages", maplit::btreemap!{ + /// "channel".into() => 1.into(), + /// }).await?; + /// # Ok(()) + /// # } + pub fn watch_all(&self) -> QuerySetSubscription { + QuerySetSubscription::new(BroadcastStream::new(self.watch_receiver.resubscribe())) + } + + /// Set auth for use when calling Convex functions. + /// + /// Set it with a token that you get from your auth provider via their login + /// flow. If `None` is passed as the token, then auth is unset (logging + /// out). + /// + /// Internally this wraps the static token in a trivial callback and the + /// same token is re-sent on websocket reconnect. + /// + ///
+ /// + /// Prefer [`ConvexClient::set_auth_callback``] - it will allow fetching a + /// fresh token after a websocket reconnect. That's important because + /// the original token might have expired while the socket was + /// disconnected. + /// + ///
+ pub async fn set_auth(&mut self, token: Option) { + let fetcher: Option = token.map(|t| { + Box::new(move |_force_refresh: bool| { + let t = t.clone(); + Box::pin(async move { Ok(AuthenticationToken::User(t)) }) + as Pin> + Send>> + }) as AuthTokenFetcher + }); + self.request_sender + .send(ClientRequest::Authenticate(fetcher)) + .expect("INTERNAL BUG: Worker has gone away"); + } + + /// Set an auth token fetcher callback for use when calling Convex + /// functions. + /// + /// The callback is invoked immediately (with `force_refresh=false`) and + /// again on every websocket reconnect (with `force_refresh=true`), + /// allowing dynamic token refresh. + /// + /// Pass `None` to clear the callback and log out. + pub async fn set_auth_callback(&mut self, fetcher: Option) { + self.request_sender + .send(ClientRequest::Authenticate(fetcher)) + .expect("INTERNAL BUG: Worker has gone away"); + } + + /// Force the client's WebSocket to reconnect and replay its current auth, + /// subscriptions, and in-flight mutations. + pub async fn reconnect_now(&mut self, reason: &str) { + self.request_sender + .send(ClientRequest::Reconnect(reason.to_owned())) + .expect("INTERNAL BUG: Worker has gone away"); + } + + /// Set admin auth for use when calling Convex functions as a deployment + /// admin. Not typically required. + /// + /// You can get a deploy_key from the Convex dashboard's deployment settings + /// page. Deployment admins can act as users as part of their + /// development flow to see how a function would act. + #[doc(hidden)] + pub async fn set_admin_auth( + &mut self, + deploy_key: String, + acting_as: Option, + ) { + let fetcher: AuthTokenFetcher = Box::new(move |_force_refresh: bool| { + let deploy_key = deploy_key.clone(); + let acting_as = acting_as.clone(); + Box::pin(async move { Ok(AuthenticationToken::Admin(deploy_key, acting_as)) }) + }); + self.request_sender + .send(ClientRequest::Authenticate(Some(fetcher))) + .expect("INTERNAL BUG: Worker has gone away"); + } +} + +fn deployment_to_ws_url(mut deployment_url: Url) -> anyhow::Result { + let ws_scheme = match deployment_url.scheme() { + "http" | "ws" => "ws", + "https" | "wss" => "wss", + scheme => anyhow::bail!("Unknown scheme {scheme}. Expected http or https."), + }; + deployment_url + .set_scheme(ws_scheme) + .expect("Scheme not supported"); + deployment_url.set_path("api/sync"); + Ok(deployment_url) +} + +/// A builder for creating a [`ConvexClient`] with custom configuration. +pub struct ConvexClientBuilder { + deployment_url: String, + client_id: Option, + on_state_change: Option>, +} + +impl ConvexClientBuilder { + /// Create a new [`ConvexClientBuilder`] with the given deployment URL. + pub fn new(deployment_url: &str) -> Self { + Self { + deployment_url: deployment_url.to_string(), + client_id: None, + on_state_change: None, + } + } + + /// Set a custom client ID for this client. + pub fn with_client_id(mut self, client_id: &str) -> Self { + self.client_id = Some(client_id.to_string()); + self + } + + /// Set a channel to be notified of changes to the WebSocket connection + /// state. + pub fn with_on_state_change(mut self, on_state_change: mpsc::Sender) -> Self { + self.on_state_change = Some(on_state_change); + self + } + + /// Build the [`ConvexClient`] with the configured options. + /// + /// ```no_run + /// # use convex::ConvexClientBuilder; + /// # #[tokio::main] + /// # async fn main() -> anyhow::Result<()> { + /// let client = ConvexClientBuilder::new("https://cool-music-123.convex.cloud").build().await?; + /// # Ok(()) + /// # } + /// ``` + pub async fn build(self) -> anyhow::Result { + ConvexClient::new_from_builder(self).await + } +} + +#[cfg(test)] +pub mod tests { + use std::{ + str::FromStr, + sync::Arc, + time::Duration, + }; + + use convex_sync_types::{ + types::SerializedArgs, + AuthenticationToken, + ClientMessage, + LogLinesMessage, + Query, + QueryId, + QuerySetModification, + SessionId, + StateModification, + StateVersion, + UdfPath, + UserIdentityAttributes, + }; + use futures::StreamExt; + use maplit::btreemap; + use pretty_assertions::assert_eq; + use serde_json::json; + use tokio::sync::{ + broadcast, + mpsc, + }; + + use super::ConvexClient; + use crate::{ + base_client::FunctionResult, + client::{ + deployment_to_ws_url, + worker::worker, + BaseConvexClient, + }, + sync::{ + testing::TestProtocolManager, + ServerMessage, + SyncProtocol, + }, + value::Value, + QuerySubscription, + }; + + impl ConvexClient { + pub async fn with_test_protocol() -> anyhow::Result<(Self, TestProtocolManager)> { + let _ = tracing_subscriber::fmt() + .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) + .try_init(); + + // Channels for the `listen` background thread + let (response_sender, response_receiver) = mpsc::channel(1); + let (request_sender, request_receiver) = mpsc::unbounded_channel(); + + // Listener for when each transaction completes + let (watch_sender, watch_receiver) = broadcast::channel(1); + + let test_protocol = TestProtocolManager::open( + "ws://test.com".parse()?, + response_sender, + None, + "rust-0.0.1", + ) + .await?; + let base_client = BaseConvexClient::new(); + + let listen_handle = tokio::spawn(worker( + response_receiver, + request_receiver, + watch_sender, + base_client, + test_protocol.clone(), + )); + + let client = ConvexClient { + listen_handle: Some(Arc::new(listen_handle)), + request_sender, + watch_receiver, + }; + Ok((client, test_protocol)) + } + } + + fn fake_mutation_response(result: FunctionResult) -> (ServerMessage, ServerMessage) { + let (transition_response, new_version) = fake_transition(StateVersion::initial(), vec![]); + let mutation_response = ServerMessage::MutationResponse { + request_id: 0, + result: result.into(), + ts: Some(new_version.ts), + log_lines: LogLinesMessage(vec![]), + }; + (mutation_response, transition_response) + } + + fn fake_action_response(result: FunctionResult) -> ServerMessage { + ServerMessage::ActionResponse { + request_id: 0, + result: result.into(), + log_lines: LogLinesMessage(vec![]), + } + } + + fn fake_transition( + start_version: StateVersion, + modifications: Vec<(QueryId, Value)>, + ) -> (ServerMessage, StateVersion) { + let end_version = StateVersion { + ts: start_version.ts.succ().expect("Succ failed"), + ..start_version + }; + ( + ServerMessage::Transition { + start_version, + end_version, + modifications: modifications + .into_iter() + .map(|(query_id, value)| StateModification::QueryUpdated { + query_id, + value, + journal: None, + log_lines: LogLinesMessage(vec![]), + }) + .collect(), + client_clock_skew: None, + server_ts: None, + }, + end_version, + ) + } + + #[tokio::test] + async fn test_mutation() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + let mut res = + tokio::spawn(async move { client.mutation("incrementCounter", btreemap! {}).await }); + test_protocol.wait_until_n_messages_sent(1).await; + + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Mutation { + request_id: 0, + udf_path: UdfPath::from_str("incrementCounter")?, + args: SerializedArgs::from_args(vec![json!({})])?, + component_path: None, + }] + ); + + let mutation_result = FunctionResult::Value(Value::Null); + let (mut_resp, transition) = fake_mutation_response(mutation_result.clone()); + test_protocol.fake_server_response(mut_resp).await?; + // Should not be ready until transition completes. + tokio::time::timeout(Duration::from_millis(50), &mut res) + .await + .unwrap_err(); + + // Once transition is sent, it is ready. + test_protocol.fake_server_response(transition).await?; + assert_eq!(res.await??, mutation_result); + Ok(()) + } + + #[tokio::test] + async fn test_mutation_error() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + let res = + tokio::spawn(async move { client.mutation("incrementCounter", btreemap! {}).await }); + test_protocol.wait_until_n_messages_sent(1).await; + test_protocol.take_sent().await; + + let mutation_result = FunctionResult::ErrorMessage("JEEPERS".into()); + let (mut_resp, _transition) = fake_mutation_response(mutation_result.clone()); + test_protocol.fake_server_response(mut_resp).await?; + // Errors should be ready immediately (no transition needed) + assert_eq!(res.await??, mutation_result); + + Ok(()) + } + + #[tokio::test] + async fn test_action() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + let action_result = FunctionResult::Value(Value::Null); + let server_message = fake_action_response(action_result.clone()); + + let res = tokio::spawn(async move { client.action("runAction:hello", btreemap! {}).await }); + test_protocol.wait_until_n_messages_sent(1).await; + + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Action { + request_id: 0, + udf_path: UdfPath::from_str("runAction:hello")?, + args: SerializedArgs::from_args(vec![json!({})])?, + component_path: None, + }] + ); + + test_protocol.fake_server_response(server_message).await?; + assert_eq!(res.await??, action_result); + Ok(()) + } + + #[tokio::test] + async fn test_auth() -> anyhow::Result<()> { + let (mut client, test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + // Set token + client.set_auth(Some("myauthtoken".into())).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 0, + token: AuthenticationToken::User("myauthtoken".into()), + }] + ); + + // Unset token + client.set_auth(None).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 1, + token: AuthenticationToken::None, + }] + ); + + // Set admin auth + client.set_admin_auth("myadminauth".into(), None).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 2, + token: AuthenticationToken::Admin("myadminauth".into(), None), + }] + ); + + // Set admin auth acting as user + let acting_as = UserIdentityAttributes { + name: Some("Barbara Liskov".into()), + ..Default::default() + }; + client + .set_admin_auth("myadminauth".into(), Some(acting_as.clone())) + .await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 3, + token: AuthenticationToken::Admin("myadminauth".into(), Some(acting_as)), + }] + ); + Ok(()) + } + + #[tokio::test] + async fn test_auth_callback() -> anyhow::Result<()> { + let (mut client, test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + // Set auth via callback + let fetcher: crate::client::AuthTokenFetcher = Box::new(|_force_refresh| { + Box::pin(async { Ok(AuthenticationToken::User("callback_token".into())) }) + }); + client.set_auth_callback(Some(fetcher)).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 0, + token: AuthenticationToken::User("callback_token".into()), + }] + ); + + // Clear auth via callback + client.set_auth_callback(None).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 1, + token: AuthenticationToken::None, + }] + ); + Ok(()) + } + + #[tokio::test] + async fn test_auth_callback_returning_none() -> anyhow::Result<()> { + let (mut client, test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + // Callback that returns None (no token) + let fetcher: crate::client::AuthTokenFetcher = + Box::new(|_force_refresh| Box::pin(async { Ok(AuthenticationToken::None) })); + client.set_auth_callback(Some(fetcher)).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 0, + token: AuthenticationToken::None, + }] + ); + Ok(()) + } + + #[tokio::test] + async fn test_set_auth_uses_callback_path() -> anyhow::Result<()> { + let (mut client, test_protocol) = ConvexClient::with_test_protocol().await?; + test_protocol.take_sent().await; + + // set_auth with a token should send the same Authenticate message as before + client.set_auth(Some("static_token".into())).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 0, + token: AuthenticationToken::User("static_token".into()), + }] + ); + + // set_auth(None) clears auth + client.set_auth(None).await; + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::Authenticate { + base_version: 1, + token: AuthenticationToken::None, + }] + ); + Ok(()) + } + + #[tokio::test] + async fn test_client_single_subscription() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + + let mut subscription1 = client.subscribe("getValue1", btreemap! {}).await?; + let query_id = subscription1.query_id(); + assert_eq!( + test_protocol.take_sent().await, + vec![ + ClientMessage::Connect { + session_id: SessionId::nil(), + connection_count: 0, + last_close_reason: "InitialConnect".to_string(), + max_observed_timestamp: None, + client_ts: None, + }, + ClientMessage::ModifyQuerySet { + base_version: 0, + new_version: 1, + modifications: vec![QuerySetModification::Add(Query { + query_id, + udf_path: "getValue1".parse()?, + args: SerializedArgs::from_args(vec![json!({})])?, + journal: None, + component_path: None, + })] + }, + ] + ); + + test_protocol + .fake_server_response( + fake_transition( + StateVersion::initial(), + vec![(subscription1.query_id(), 10.into())], + ) + .0, + ) + .await?; + assert_eq!( + subscription1.next().await, + Some(FunctionResult::Value(10.into())) + ); + assert_eq!( + client.query("getValue1", btreemap! {}).await?, + FunctionResult::Value(10.into()) + ); + + drop(subscription1); + test_protocol.wait_until_n_messages_sent(1).await; + assert_eq!( + test_protocol.take_sent().await, + vec![ClientMessage::ModifyQuerySet { + base_version: 1, + new_version: 2, + modifications: vec![QuerySetModification::Remove { query_id }], + }] + ); + + Ok(()) + } + + #[tokio::test] + async fn test_client_subscribe_unsubscribe_subscribe() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + let subscription1b: QuerySubscription; + { + // This subscription goes out of scope and unsubscribes at the end of this + // block. The internal num_subscribers value gets decremented. + let _ignored = client.subscribe("getValue1", btreemap! {}).await?; + subscription1b = client.subscribe("getValue1", btreemap! {}).await?; + } + // In the buggy scenario, this subscription gets an ID via num_subscribers ID + // that matches subscription1b. That triggers a panic. + let subscription1c = client.subscribe("getValue1", btreemap! {}).await?; + test_protocol.take_sent().await; + let mut watch = client.watch_all(); + + test_protocol + .fake_server_response( + fake_transition(StateVersion::initial(), vec![(QueryId::new(0), 10.into())]).0, + ) + .await?; + + let results = watch.next().await.expect("Watch should have results"); + assert_eq!( + results.get(&subscription1b), + Some(&FunctionResult::Value(10.into())) + ); + assert_eq!( + results.get(&subscription1c), + Some(&FunctionResult::Value(10.into())) + ); + Ok(()) + } + + #[tokio::test] + async fn test_client_consistent_view_watch() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + let subscription1 = client.subscribe("getValue1", btreemap! {}).await?; + let subscription2a = client.subscribe("getValue2", btreemap! {}).await?; + let subscription2b = client.subscribe("getValue2", btreemap! {}).await?; + let subscription3 = client.subscribe("getValue3", btreemap! {}).await?; + test_protocol.take_sent().await; + let mut watch = client.watch_all(); + + test_protocol + .fake_server_response( + fake_transition( + StateVersion::initial(), + vec![(QueryId::new(0), 10.into()), (QueryId::new(1), 20.into())], + ) + .0, + ) + .await?; + + let results = watch.next().await.expect("Watch should have results"); + assert_eq!( + results.get(&subscription1), + Some(&FunctionResult::Value(10.into())) + ); + assert_eq!( + results.get(&subscription2a), + Some(&FunctionResult::Value(20.into())) + ); + assert_eq!( + results.get(&subscription2b), + Some(&FunctionResult::Value(20.into())) + ); + assert_eq!(results.get(&subscription3), None); + assert_eq!( + results.iter().collect::>(), + vec![ + (subscription1.id(), Some(&FunctionResult::Value(10.into()))), + (subscription2a.id(), Some(&FunctionResult::Value(20.into()))), + (subscription2b.id(), Some(&FunctionResult::Value(20.into()))), + (subscription3.id(), None,), + ] + ); + + // Ideally a new watch should immediately give you results, but we don't have + // that yet. Need to replace tokio::broadcast with something that buffers 1 + // item. + //let mut watch2 = client.watch(); + //let results = watch.next().await.expect("Watch should have results"); + //assert_eq!(results.len(), 3); + + Ok(()) + } + + #[tokio::test] + async fn test_drop_client() -> anyhow::Result<()> { + let (mut client, _test_protocol) = ConvexClient::with_test_protocol().await?; + let mut subscription1 = client.subscribe("getValue1", btreemap! {}).await?; + drop(client); + tokio::task::yield_now().await; + assert!(subscription1.next().await.is_none()); + drop(subscription1); + Ok(()) + } + + #[tokio::test] + async fn test_client_separate_queries() -> anyhow::Result<()> { + let (mut client, test_protocol) = ConvexClient::with_test_protocol().await?; + + // All three of these should be considered separate + let subscription1 = client.subscribe("getValue1", btreemap! {}).await?; + let subscription2 = client.subscribe("getValue2", btreemap! {}).await?; + let subscription3 = client + .subscribe("getValue2", btreemap! {"hello".into() => "world".into()}) + .await?; + assert_ne!(subscription1.query_id(), subscription2.query_id()); + assert_ne!(subscription2.query_id(), subscription3.query_id()); + + assert_eq!( + test_protocol.take_sent().await, + vec![ + ClientMessage::Connect { + session_id: SessionId::nil(), + connection_count: 0, + last_close_reason: "InitialConnect".to_string(), + max_observed_timestamp: None, + client_ts: None, + }, + ClientMessage::ModifyQuerySet { + base_version: 0, + new_version: 1, + modifications: vec![QuerySetModification::Add(Query { + query_id: subscription1.query_id(), + udf_path: "getValue1".parse()?, + args: SerializedArgs::from_args(vec![json!({})])?, + journal: None, + component_path: None, + })] + }, + ClientMessage::ModifyQuerySet { + base_version: 1, + new_version: 2, + modifications: vec![QuerySetModification::Add(Query { + query_id: subscription2.query_id(), + udf_path: "getValue2".parse()?, + args: SerializedArgs::from_args(vec![json!({})])?, + journal: None, + component_path: None, + })] + }, + ClientMessage::ModifyQuerySet { + base_version: 2, + new_version: 3, + modifications: vec![QuerySetModification::Add(Query { + query_id: subscription3.query_id(), + udf_path: "getValue2".parse()?, + args: SerializedArgs::from_args(vec![json!({"hello": "world"})])?, + journal: None, + component_path: None, + })] + }, + ] + ); + + Ok(()) + } + + #[tokio::test] + async fn test_client_two_identical_queries() -> anyhow::Result<()> { + let (mut client, mut test_protocol) = ConvexClient::with_test_protocol().await?; + + // These two should be considered the same query. + let mut subscription1 = client.subscribe("getValue", btreemap! {}).await?; + let mut subscription2 = client.subscribe("getValue", btreemap! {}).await?; + + assert_ne!(subscription1.subscriber_id, subscription2.subscriber_id); + assert_eq!(subscription1.query_id(), subscription2.query_id()); + let query_id = subscription1.query_id(); + + assert_eq!( + test_protocol.take_sent().await, + vec![ + ClientMessage::Connect { + session_id: SessionId::nil(), + connection_count: 0, + last_close_reason: "InitialConnect".to_string(), + max_observed_timestamp: None, + client_ts: None, + }, + ClientMessage::ModifyQuerySet { + base_version: 0, + new_version: 1, + modifications: vec![QuerySetModification::Add(Query { + query_id, + udf_path: "getValue".parse()?, + args: SerializedArgs::from_args(vec![json!({})])?, + journal: None, + component_path: None, + })] + }, + ] + ); + + let mut version = StateVersion::initial(); + for i in 1..5 { + let (transition, new_version) = fake_transition(version, vec![(query_id, i.into())]); + test_protocol.fake_server_response(transition).await?; + version = new_version; + + assert_eq!( + subscription1.next().await, + Some(FunctionResult::Value(i.into())) + ); + assert_eq!( + subscription2.next().await, + Some(FunctionResult::Value(i.into())) + ); + } + + // A new subscription should auto-initialize with the value if available + let mut subscription3 = client.subscribe("getValue", btreemap! {}).await?; + assert_eq!( + subscription3.next().await, + Some(FunctionResult::Value(4.into())), + ); + + // Dropping sub1 and sub2 should still maintain subscription + drop(subscription1); + drop(subscription2); + let (transition, _new_version) = fake_transition(version, vec![(query_id, 5.into())]); + test_protocol.fake_server_response(transition).await?; + assert_eq!( + subscription3.next().await, + Some(FunctionResult::Value(5.into())), + ); + + Ok(()) + } + + #[test] + fn test_deployment_url() -> anyhow::Result<()> { + assert_eq!( + deployment_to_ws_url("http://flying-shark-123.convex.cloud".parse()?)?.to_string(), + "ws://flying-shark-123.convex.cloud/api/sync", + ); + assert_eq!( + deployment_to_ws_url("https://flying-shark-123.convex.cloud".parse()?)?.to_string(), + "wss://flying-shark-123.convex.cloud/api/sync", + ); + assert_eq!( + deployment_to_ws_url("ws://flying-shark-123.convex.cloud".parse()?)?.to_string(), + "ws://flying-shark-123.convex.cloud/api/sync", + ); + assert_eq!( + deployment_to_ws_url("wss://flying-shark-123.convex.cloud".parse()?)?.to_string(), + "wss://flying-shark-123.convex.cloud/api/sync", + ); + assert_eq!( + deployment_to_ws_url("ftp://flying-shark-123.convex.cloud".parse()?) + .unwrap_err() + .to_string(), + "Unknown scheme ftp. Expected http or https.", + ); + Ok(()) + } +} diff --git a/third_party/convex_rs/src/client/subscription.rs b/third_party/convex_rs/src/client/subscription.rs new file mode 100644 index 00000000..0f3b4786 --- /dev/null +++ b/third_party/convex_rs/src/client/subscription.rs @@ -0,0 +1,149 @@ +use std::{ + ops::Deref, + pin::Pin, +}; + +use futures::{ + task, + Stream, + StreamExt, +}; +use tokio::sync::mpsc; +use tokio_stream::wrappers::{ + errors::BroadcastStreamRecvError, + BroadcastStream, +}; + +use crate::{ + base_client::{ + FunctionResult, + QueryResults, + SubscriberId, + }, + client::worker::{ + ClientRequest, + UnsubscribeRequest, + }, +}; +#[cfg(doc)] +use crate::{ + ConvexClient, + Value, +}; + +/// This structure represents a single subscription to a query with args. +/// For convenience, [`QuerySubscription`] also implements +/// [`Stream`]<[`FunctionResult`]>, giving a stream of results to the query. +/// +/// It is returned by [`ConvexClient::subscribe`]. The subscription lives +/// in the active query set for as long as this token stays in scope. +/// +/// For a consistent [`QueryResults`] of all your queries, use +/// [`ConvexClient::watch_all()`] instead. +pub struct QuerySubscription { + pub(super) subscriber_id: SubscriberId, + pub(super) request_sender: mpsc::UnboundedSender, + pub(super) watch: BroadcastStream, + pub(super) initial: Option, +} +impl QuerySubscription { + /// Returns an identifier for this subscription based on its query and args. + /// This identifier can be used to find the result within a + /// [`QuerySetSubscription`] as returned by [`ConvexClient::watch_all()`] + pub fn id(&self) -> &SubscriberId { + &self.subscriber_id + } +} +impl std::fmt::Debug for QuerySubscription { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("QuerySubscription") + .field("subscriber_id", &self.subscriber_id) + .finish() + } +} +impl Deref for QuerySubscription { + type Target = SubscriberId; + + fn deref(&self) -> &SubscriberId { + &self.subscriber_id + } +} +impl Drop for QuerySubscription { + fn drop(&mut self) { + let _ = self + .request_sender + .send(ClientRequest::Unsubscribe(UnsubscribeRequest { + subscriber_id: self.subscriber_id, + })); + } +} +impl Stream for QuerySubscription { + type Item = FunctionResult; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> task::Poll> { + if let Some(initial) = self.initial.take() { + return task::Poll::Ready(Some(initial)); + } + loop { + return match self.watch.poll_next_unpin(cx) { + // Ok to be lagged (skip intermediate values) - since Convex + // only guarantees a newer value than the previous value. + task::Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_amt)))) => continue, + task::Poll::Ready(Some(Ok(map))) => { + let Some(value) = map.get(self.id()) else { + // No result yet in the query result set. Keep polling. + continue; + }; + task::Poll::Ready(Some(value.clone())) + }, + task::Poll::Ready(None) => task::Poll::Ready(None), + task::Poll::Pending => task::Poll::Pending, + }; + } + } +} + +/// A subscription to a consistent view of multiple queries. +/// +/// [`QuerySetSubscription`] +/// implements [`Stream`]<[`QueryResults`]>. +/// Each item in the stream contains a consistent view +/// of the results of all the queries in the query set. +/// +/// Queries can be added to the query set via [`ConvexClient::subscribe`]. +/// Queries can be removed from the query set via dropping the +/// [`QuerySubscription`] token returned by [`ConvexClient::subscribe`]. +/// +/// +/// [`QueryResults`] is a copy-on-write mapping from [`SubscriberId`] to +/// its latest result [`Value`]. +pub struct QuerySetSubscription { + watch: BroadcastStream, +} +impl QuerySetSubscription { + pub(super) fn new(watch: BroadcastStream) -> Self { + Self { watch } + } +} +impl Stream for QuerySetSubscription { + type Item = QueryResults; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> task::Poll> { + loop { + return match self.watch.poll_next_unpin(cx) { + // Ok to be lagged (skip intermediate values) - since Convex + // only guarantees a newer value than the previous value. + task::Poll::Ready(Some(Err(BroadcastStreamRecvError::Lagged(_amt)))) => continue, + task::Poll::Ready(Some(Ok(map))) => task::Poll::Ready(Some(map)), + task::Poll::Ready(None) => task::Poll::Ready(None), + task::Poll::Pending => task::Poll::Pending, + }; + } + } +} diff --git a/third_party/convex_rs/src/client/worker.rs b/third_party/convex_rs/src/client/worker.rs new file mode 100644 index 00000000..bd001304 --- /dev/null +++ b/third_party/convex_rs/src/client/worker.rs @@ -0,0 +1,367 @@ +use std::{ + collections::BTreeMap, + convert::Infallible, + time::Duration, +}; + +use convex_sync_types::{ + backoff::Backoff, + UdfPath, +}; +use tokio::sync::{ + broadcast, + mpsc, + oneshot, +}; +use tokio_stream::wrappers::BroadcastStream; + +use crate::{ + base_client::{ + AuthTokenFetcher, + BaseConvexClient, + SubscriberId, + }, + client::{ + QueryResults, + QuerySubscription, + }, + sync::{ + ProtocolResponse, + ReconnectProtocolReason, + ReconnectRequest, + SyncProtocol, + }, + value::Value, + FunctionResult, +}; + +const INITIAL_BACKOFF: Duration = Duration::from_millis(100); +const MAX_BACKOFF: Duration = Duration::from_secs(15); +const AUTH_RETRY_DELAY: Duration = Duration::from_millis(250); + +fn is_auth_rejection(reason: &str) -> bool { + reason.starts_with("AuthError:") +} + +#[derive(Default)] +struct AuthRecovery { + active: bool, + fresh_token_in_flight: bool, +} + +pub enum ClientRequest { + Mutation( + MutationRequest, + oneshot::Sender>, + ), + Action( + ActionRequest, + oneshot::Sender>, + ), + Subscribe( + SubscribeRequest, + oneshot::Sender, + mpsc::UnboundedSender, + ), + Unsubscribe(UnsubscribeRequest), + Authenticate(Option), + Reconnect(String), +} + +pub struct MutationRequest { + pub udf_path: UdfPath, + pub args: BTreeMap, +} + +pub struct ActionRequest { + pub udf_path: UdfPath, + pub args: BTreeMap, +} + +pub struct SubscribeRequest { + pub udf_path: UdfPath, + pub args: BTreeMap, +} + +#[derive(Debug)] +pub struct UnsubscribeRequest { + pub subscriber_id: SubscriberId, +} + +pub async fn worker( + mut protocol_response_receiver: mpsc::Receiver, + mut client_request_receiver: mpsc::UnboundedReceiver, + mut watch_sender: broadcast::Sender, + mut base_client: BaseConvexClient, + mut protocol_manager: T, +) -> Infallible { + let mut backoff = Backoff::new(INITIAL_BACKOFF, MAX_BACKOFF); + let mut auth_recovery = AuthRecovery::default(); + loop { + let e = loop { + match _worker_once( + &mut protocol_response_receiver, + &mut client_request_receiver, + &mut watch_sender, + &mut base_client, + &mut protocol_manager, + &mut auth_recovery, + ) + .await + { + Ok(()) => backoff.reset(), + Err(e) => break e, + } + }; + + if is_auth_rejection(&e) { + auth_recovery.active = true; + auth_recovery.fresh_token_in_flight = false; + } + + // A server auth rejection and the protocol errors cascading from that + // rejected socket are not network outages. The reconnect below + // invokes the stored token callback with `force_refresh=true`, so retry + // it at a small fixed cadence while the auth provider obtains a fresh + // token. Applying the generic exponential network backoff here can + // otherwise delay a ready token for up to MAX_BACKOFF. + let delay = if auth_recovery.active { + backoff.reset(); + AUTH_RETRY_DELAY + } else { + backoff.fail(&mut rand::rng()) + }; + tracing::error!( + "Convex Client Worker failed: {e:?}. Backing off for {delay:?} and retrying." + ); + tokio::time::sleep(delay).await; + + // Everything currently buffered came from the connection we are about + // to replace. In particular, an auth error can be followed by the old + // socket closing and queuing ProtocolFailure while this worker is in + // its retry delay. Replaying that stale failure after the new socket is + // up starts a second, unrelated backoff and can leave fresh auth behind + // it. Query and mutation state is rebuilt below, so discard the old + // connection's buffered responses before requesting the replacement. + while protocol_response_receiver.try_recv().is_ok() {} + + // Tell the sync protocol to reconnect followed by an immediate resend of + // ongoing queries/mutations. It's important these happen together to + // ensure mutation ordering. If an auth token fetcher is stored, + // resend_ongoing_queries_mutations will refresh the token first. + protocol_manager + .reconnect(ReconnectRequest { + reason: e, + max_observed_timestamp: base_client.max_observed_timestamp(), + auth_retry: auth_recovery.active, + }) + .await; + let auth_token_changed = base_client.resend_ongoing_queries_mutations().await; + if auth_recovery.active && auth_token_changed { + auth_recovery.fresh_token_in_flight = true; + } + // We'll flush messages from base_client inside the next call to + // `_worker_once`. + } +} + +#[cfg(test)] +mod tests { + use super::is_auth_rejection; + + #[test] + fn only_server_auth_errors_use_the_auth_retry_path() { + assert!(is_auth_rejection( + "AuthError: token expired for identity version 1" + )); + assert!(!is_auth_rejection("ProtocolFailure")); + assert!(!is_auth_rejection("convex_flutter:manual")); + } +} + +async fn _worker_once( + protocol_response_receiver: &mut mpsc::Receiver, + client_request_receiver: &mut mpsc::UnboundedReceiver, + watch_sender: &mut broadcast::Sender, + base_client: &mut BaseConvexClient, + protocol_manager: &mut T, + auth_recovery: &mut AuthRecovery, +) -> Result<(), ReconnectProtocolReason> { + // If there are any outgoing messages to flush (e.g. from an outer reconnect), + // do so first. + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + + tokio::select! { + Some(protocol_response) = protocol_response_receiver.recv() => { + handle_protocol_response( + base_client, + watch_sender, + protocol_response, + auth_recovery, + )?; + } + Some(client_request) = client_request_receiver.recv() => { + match client_request { + ClientRequest::Subscribe(query, tx, request_sender) => { + let watch = watch_sender.subscribe(); + let SubscribeRequest { + udf_path, + args, + } = query; + let subscriber_id = base_client.subscribe(udf_path, args); + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + + let watch = BroadcastStream::new(watch); + let subscription = QuerySubscription { + subscriber_id, + request_sender, + watch, + initial: base_client.latest_results().get(&subscriber_id).cloned(), + }; + let _ = tx.send(subscription); + }, + ClientRequest::Mutation(mutation, tx) => { + let MutationRequest { + udf_path, + args, + } = mutation; + let result_receiver = base_client + .mutation(udf_path, args); + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + let _ = tx.send(result_receiver); + }, + ClientRequest::Action(action, tx) => { + let ActionRequest { + udf_path, + args, + } = action; + let result_receiver = base_client + .action(udf_path, args); + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + let _ = tx.send(result_receiver); + }, + ClientRequest::Unsubscribe(unsubscribe) => { + let UnsubscribeRequest {subscriber_id} = unsubscribe; + base_client.unsubscribe(subscriber_id); + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + }, + ClientRequest::Authenticate(fetcher) => { + let token_changed = base_client.set_auth_fetcher(fetcher).await; + if auth_recovery.active && token_changed { + auth_recovery.fresh_token_in_flight = true; + } + communicate( + base_client, + protocol_response_receiver, + watch_sender, + protocol_manager, + auth_recovery, + ) + .await?; + }, + ClientRequest::Reconnect(reason) => return Err(reason), + } + }, + // TODO: this else branch will lead to an infinite loop if both channels + // are closed + else => (), + } + Ok(()) +} + +/// Flush all messages to the protocol while processing server mesages. +async fn communicate( + base_client: &mut BaseConvexClient, + protocol_response_receiver: &mut mpsc::Receiver, + watch_sender: &mut broadcast::Sender, + protocol: &mut P, + auth_recovery: &mut AuthRecovery, +) -> Result<(), ReconnectProtocolReason> { + while let Some(modification) = base_client.pop_next_message() { + let mut send_future = protocol.send(modification); + loop { + tokio::select! { + _ = &mut send_future => break, + // Keep processing protocol responses while waiting so that we + // don't deadlock with the websocket worker. + Some(protocol_response) = protocol_response_receiver.recv() => { + handle_protocol_response( + base_client, + watch_sender, + protocol_response, + auth_recovery, + )?; + } + } + } + } + Ok(()) +} + +fn handle_protocol_response( + base_client: &mut BaseConvexClient, + watch_sender: &mut broadcast::Sender, + protocol_response: ProtocolResponse, + auth_recovery: &mut AuthRecovery, +) -> Result<(), ReconnectProtocolReason> { + match protocol_response { + ProtocolResponse::ServerMessage(msg) => { + let proves_protocol_auth = matches!( + &msg, + crate::sync::ServerMessage::Transition { .. } + | crate::sync::ServerMessage::MutationResponse { .. } + | crate::sync::ServerMessage::ActionResponse { .. } + ); + if let Some(subscriber_id_to_latest_value) = base_client.receive_message(msg)? { + // Notify watchers of the new consistent query results at new timestamp + let _ = watch_sender.send(subscriber_id_to_latest_value); + } + if proves_protocol_auth && auth_recovery.fresh_token_in_flight { + // Only a response after the refresh callback produced a + // different token proves recovery. Transitions buffered while + // the rejected token was still current cannot clear this state. + auth_recovery.active = false; + auth_recovery.fresh_token_in_flight = false; + } + }, + ProtocolResponse::Failure => { + return Err("ProtocolFailure".into()); + }, + } + Ok(()) +} diff --git a/third_party/convex_rs/src/lib.rs b/third_party/convex_rs/src/lib.rs new file mode 100644 index 00000000..f87b14bc --- /dev/null +++ b/third_party/convex_rs/src/lib.rs @@ -0,0 +1,75 @@ +//! # Convex Client +//! The official Rust client for [Convex](https://convex.dev). +//! +//! Convex is the backend application platform with everything you need to build +//! your product. Convex clients can subscribe to queries and perform mutations +//! and actions. Check out the [Convex Documentation](https://docs.convex.dev) for more information. +//! +//! # Usage +//! ## Native Rust development +//! To use Convex to create native Rust applications with [`tokio`], you can use +//! the [`ConvexClient`] struct directly. All you need is your deployment URL +//! from your existing project, and you can subscribe to queries and call +//! mutations. To make a new project, check out our [getting started guide](https://docs.convex.dev/get-started). +//! +//! ```no_run +//! use convex::ConvexClient; +//! use futures::StreamExt; +//! +//! #[tokio::main] +//! async fn main() -> anyhow::Result<()> { +//! let mut client = ConvexClient::new("https://cool-music-123.convex.cloud").await?; +//! client.mutation("sendMessage", maplit::btreemap!{ +//! "body".into() => "Let it be.".into(), +//! "author".into() => "The Beatles".into(), +//! }).await?; +//! let mut sub = client.subscribe("listMessages", maplit::btreemap!{}).await?; +//! while let Some(result) = sub.next().await { +//! println!("{result:?}"); +//! } +//! Ok(()) +//! } +//! ``` +//! +//! ## Extending client for other programming languages or frameworks. +//! To extend Convex into non-[`tokio`] frameworks, +//! you can use the [`base_client::BaseConvexClient`] to build something similar +//! to a [`ConvexClient`]. +//! +//! Detailed examples of both use cases are documented for each struct. + +#![cfg_attr(not(test), warn(missing_docs))] +#![warn(rustdoc::missing_crate_level_docs)] + +mod value; +#[cfg(any(test, feature = "testing"))] +pub use value::export::roundtrip::ExportContext; +pub use value::{ + ConvexError, + Value, +}; + +mod client; +pub use client::{ + subscription::{ + QuerySetSubscription, + QuerySubscription, + }, + ConvexClient, + ConvexClientBuilder, +}; +#[cfg(any(test, feature = "testing"))] +pub use sync::testing; +pub use sync::WebSocketState; + +pub mod base_client; +#[doc(inline)] +pub use base_client::{ + AuthTokenFetcher, + FunctionResult, + QueryResults, + SubscriberId, +}; +pub use convex_sync_types::AuthenticationToken; + +mod sync; diff --git a/third_party/convex_rs/src/sync/mod.rs b/third_party/convex_rs/src/sync/mod.rs new file mode 100644 index 00000000..a3dc1b2a --- /dev/null +++ b/third_party/convex_rs/src/sync/mod.rs @@ -0,0 +1,56 @@ +use async_trait::async_trait; +use convex_sync_types::{ + ClientMessage, + Timestamp, +}; +use tokio::sync::mpsc; +use url::Url; + +use crate::value::Value; + +#[cfg(any(test, feature = "testing"))] +pub mod testing; +pub mod web_socket_manager; + +/// Upon a protocol failure, an explanation of the failure to pass in on +/// reconnect +#[derive(Debug)] +pub struct ReconnectRequest { + pub reason: ReconnectProtocolReason, + pub max_observed_timestamp: Option, + /// The old socket failed while recovering a server auth rejection. The + /// client worker already paces these retries, so the WebSocket layer must + /// not add its independent network backoff. + pub auth_retry: bool, +} + +pub type ReconnectProtocolReason = String; + +pub type ServerMessage = convex_sync_types::ServerMessage; + +#[derive(Debug)] +pub enum ProtocolResponse { + ServerMessage(ServerMessage), + Failure, +} + +#[derive(Debug)] +/// The state of the Convex WebSocket connection +pub enum WebSocketState { + /// The WebSocket is open and connected + Connected, + /// The WebSocket is closed and connecting/reconnecting + Connecting, +} + +#[async_trait] +pub trait SyncProtocol: Send + Sized { + async fn open( + ws_url: Url, + on_response: mpsc::Sender, + on_state_change: Option>, + client_id: &str, + ) -> anyhow::Result; + async fn send(&mut self, message: ClientMessage) -> anyhow::Result<()>; + async fn reconnect(&mut self, request: ReconnectRequest); +} diff --git a/third_party/convex_rs/src/sync/testing.rs b/third_party/convex_rs/src/sync/testing.rs new file mode 100644 index 00000000..1e044e0e --- /dev/null +++ b/third_party/convex_rs/src/sync/testing.rs @@ -0,0 +1,106 @@ +#![allow(missing_docs)] +use std::{ + sync::Arc, + time::Duration, +}; + +use async_trait::async_trait; +use convex_sync_types::{ + ClientMessage, + SessionId, +}; +use parking_lot::Mutex; +use tokio::sync::mpsc; +use url::Url; +use uuid::Uuid; + +use super::{ + ReconnectRequest, + WebSocketState, +}; +use crate::sync::{ + ProtocolResponse, + ServerMessage, + SyncProtocol, +}; + +#[derive(Debug)] +struct TestProtocolInner { + closed: bool, + sent_messages: Vec, +} +/// TestProtocolManager +#[derive(Debug, Clone)] +pub struct TestProtocolManager { + inner: Arc>, + response_sender: mpsc::Sender, +} + +impl TestProtocolManager { + pub async fn fake_server_response(&mut self, message: ServerMessage) -> anyhow::Result<()> { + self.response_sender + .send(ProtocolResponse::ServerMessage(message)) + .await?; + Ok(()) + } + + pub async fn wait_until_n_messages_sent(&self, n: usize) { + tokio::time::timeout(Duration::from_secs(2), async { + while self.inner.lock().sent_messages.len() < n { + tokio::task::yield_now().await; + } + }) + .await + .expect("Test timed out waiting for messages to be sent"); + } + + pub async fn take_sent(&self) -> Vec { + std::mem::take(&mut self.inner.lock().sent_messages) + } +} + +#[async_trait] +impl SyncProtocol for TestProtocolManager { + async fn open( + _ws_url: Url, + response_sender: mpsc::Sender, + _on_state_change: Option>, + _client_id: &str, + ) -> anyhow::Result { + let mut test_protocol = TestProtocolManager { + inner: Arc::new(Mutex::new(TestProtocolInner { + closed: false, + sent_messages: vec![], + })), + response_sender, + }; + + let session_id = Uuid::nil(); + let connection_count = 0; + + test_protocol + .send(ClientMessage::Connect { + session_id: SessionId::new(session_id), + connection_count, + last_close_reason: "InitialConnect".to_string(), + max_observed_timestamp: None, + client_ts: None, + }) + .await?; + + Ok(test_protocol) + } + + async fn send(&mut self, message: ClientMessage) -> anyhow::Result<()> { + if self.inner.lock().closed { + anyhow::ensure!(!self.inner.lock().closed, "Websocket is closed"); + } + self.inner.lock().sent_messages.push(message); + + Ok(()) + } + + async fn reconnect(&mut self, request: ReconnectRequest) { + panic!("Test reconnected {request:?}"); + } +} diff --git a/third_party/convex_rs/src/sync/web_socket_manager.rs b/third_party/convex_rs/src/sync/web_socket_manager.rs new file mode 100644 index 00000000..8ac5b8c6 --- /dev/null +++ b/third_party/convex_rs/src/sync/web_socket_manager.rs @@ -0,0 +1,375 @@ +use std::{ + convert::Infallible, + time::Duration, +}; + +use anyhow::Context; +use async_trait::async_trait; +use convex_sync_types::{ + backoff::Backoff, + headers::{ + DEPRECATION_MSG_HEADER_NAME, + DEPRECATION_STATE_HEADER_NAME, + }, + ClientMessage, + SessionId, + Timestamp, +}; +use futures::{ + select_biased, + stream::Fuse, + FutureExt, + SinkExt, + StreamExt, +}; +use tokio::{ + net::TcpStream, + sync::{ + mpsc, + oneshot, + }, + task::JoinHandle, + time::{ + Instant, + Interval, + }, +}; +use tokio_stream::wrappers::UnboundedReceiverStream; +use tokio_tungstenite::{ + connect_async, + tungstenite::{ + self, + client::IntoClientRequest, + http::HeaderMap, + protocol::Message, + }, + MaybeTlsStream, + WebSocketStream, +}; +use url::Url; +use uuid::Uuid; + +use super::WebSocketState; +use crate::sync::{ + ProtocolResponse, + ReconnectRequest, + ServerMessage, + SyncProtocol, +}; + +const INITIAL_BACKOFF: Duration = Duration::from_millis(100); +const MAX_BACKOFF: Duration = Duration::from_secs(15); +type WsStream = WebSocketStream>; + +#[derive(Debug)] +enum WebSocketRequest { + SendMessage(ClientMessage, oneshot::Sender<()>), + Reconnect(ReconnectRequest), +} + +struct WebSocketInternal { + ws_stream: WsStream, + last_server_response: Instant, +} +struct WebSocketWorker { + ws_url: Url, + on_response: mpsc::Sender, + on_state_change: Option>, + internal_receiver: Fuse>, + ping_ticker: Interval, + session_id: SessionId, + connection_count: u32, + backoff: Backoff, +} + +pub struct WebSocketManager { + internal_sender: mpsc::UnboundedSender, + worker_handle: JoinHandle, +} +impl Drop for WebSocketManager { + fn drop(&mut self) { + self.worker_handle.abort() + } +} + +#[async_trait] +impl SyncProtocol for WebSocketManager { + async fn open( + ws_url: Url, + on_response: mpsc::Sender, + on_state_change: Option>, + client_id: &str, + ) -> anyhow::Result { + let (internal_sender, internal_receiver) = mpsc::unbounded_channel(); + let worker_handle = tokio::spawn(WebSocketWorker::run( + ws_url, + on_response, + on_state_change, + internal_receiver, + client_id.to_string(), + )); + + Ok(WebSocketManager { + internal_sender, + worker_handle, + }) + } + + async fn send(&mut self, message: ClientMessage) -> anyhow::Result<()> { + let (tx, rx) = oneshot::channel(); + self.internal_sender + .send(WebSocketRequest::SendMessage(message, tx))?; + rx.await?; + Ok(()) + } + + async fn reconnect(&mut self, request: ReconnectRequest) { + let _ = self + .internal_sender + .send(WebSocketRequest::Reconnect(request)); + } +} + +impl WebSocketWorker { + /// How often heartbeat pings are sent. + const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5); + /// How long before lack of server response causes a timeout. + const SERVER_INACTIVITY_THRESHOLD: Duration = Duration::from_secs(30); + + async fn run( + ws_url: Url, + on_response: mpsc::Sender, + on_state_change: Option>, + internal_receiver: mpsc::UnboundedReceiver, + client_id: String, + ) -> Infallible { + let ping_ticker = tokio::time::interval(Self::HEARTBEAT_INTERVAL); + let backoff = Backoff::new(INITIAL_BACKOFF, MAX_BACKOFF); + + let mut worker = Self { + ws_url, + on_response, + on_state_change, + internal_receiver: UnboundedReceiverStream::new(internal_receiver).fuse(), + ping_ticker, + session_id: SessionId::new(Uuid::new_v4()), + connection_count: 0, + backoff, + }; + + let mut last_close_reason = "InitialConnect".to_string(); + let mut max_observed_timestamp = None; + if let Some(state_change_sender) = &worker.on_state_change { + let _ = state_change_sender.try_send(WebSocketState::Connecting); + } + loop { + let exit_result = worker + .work(last_close_reason, max_observed_timestamp, &client_id) + .await; + + if let Some(state_change_sender) = &worker.on_state_change { + let _ = state_change_sender.try_send(WebSocketState::Connecting); + } + + let e = match exit_result { + Ok(reconnect) => { + // WS worker exited cleanly because it got a request to reconnect + tracing::debug!("Reconnecting websocket due to {}", reconnect.reason); + last_close_reason = reconnect.reason; + max_observed_timestamp = reconnect.max_observed_timestamp; + continue; + }, + Err(e) => e, + }; + last_close_reason = e.to_string(); + let mut delay = worker.backoff.fail(&mut rand::rng()); + tracing::error!( + "Convex WebSocketWorker failed: {e:?}. Backing off for {delay:?} and retrying." + ); + + // Tell the worker that we've failed so it can coordinate the reconnect. + // The worker will send a Reconnect message and the new query set all together. + // Drain the input request queue until we get that reconnect message - which + // will be followed by the refreshed query set. + let _ = worker.on_response.send(ProtocolResponse::Failure).await; + tracing::debug!("Waiting for base client to acknowledge reconnect"); + let reconnect = loop { + let request = worker.internal_receiver.next().await; + // TODO: There is a potential issue where we have multiple queued reconnect + // requests in which case max_observed_timestamp might be lower than actually + // observed. This is fine since it will never cause errors. Will can fix this + // when we restructure the wider protocol to be a single routine. + if let Some(WebSocketRequest::Reconnect(reconnect)) = request { + max_observed_timestamp = reconnect.max_observed_timestamp; + break reconnect; + } + }; + if reconnect.auth_retry { + // Auth retries are paced by the client worker while it polls + // the token callback. A second exponential backoff here can + // leave an already refreshed credential idle for 15 seconds. + worker.backoff.reset(); + delay = Duration::ZERO; + } + tracing::debug!( + "Base client acknowledged reconnect. Sleeping {delay:?} and reconnecting" + ); + tokio::time::sleep(delay).await; + tracing::debug!("Reconnecting"); + } + } + + async fn work( + &mut self, + last_close_reason: String, + max_seen_transition: Option, + client_id: &str, + ) -> anyhow::Result { + let verb = if self.connection_count == 0 { + "connect" + } else { + "reconnect" + }; + tracing::debug!("trying to {verb} to {}", self.ws_url); + let mut internal = WebSocketInternal::new( + self.ws_url.clone(), + self.session_id, + self.connection_count, + last_close_reason, + max_seen_transition, + client_id, + ) + .await?; + // One client owns one session ID and advances the connection count for + // every socket that opens, including client-requested reconnects. + self.connection_count += 1; + tracing::debug!("completed websocket {verb} to {}", self.ws_url); + if let Some(state_change_sender) = &self.on_state_change { + let _ = state_change_sender.try_send(WebSocketState::Connected); + } + + loop { + select_biased! { + _ = self.ping_ticker.tick().fuse() => { + let now = Instant::now(); + if now - internal.last_server_response > Self::SERVER_INACTIVITY_THRESHOLD { + anyhow::bail!("InactiveServer"); + } + }, + server_msg = internal.ws_stream.select_next_some() => { + internal.last_server_response = Instant::now(); + + match server_msg.context("WebsocketConnectionError")? { + Message::Close(close_frame) => { + let close_frame = close_frame.context("CloseMessageWithoutFrame")?; + tracing::debug!("Close frame {close_frame}"); + anyhow::bail!("{}", close_frame.reason); + }, + Message::Text(t) => { + let json: serde_json::Value = serde_json::from_str(&t).context("JsonDeserializeError")?; + let server_message = json.try_into()?; + match server_message { + ServerMessage::Ping => tracing::trace!("received message {server_message:?}"), + _ => tracing::trace!("received message {server_message:?}"), + }; + + let resp = ProtocolResponse::ServerMessage(server_message); + let _ = self.on_response.send(resp).await; + + // TODO: Similar to JS, we should ideally only reset backoff if we get + // the client gets into a correct state, where we have Connected and + // received a response to our pending Queries and Mutations. + self.backoff.reset(); + }, + Message::Ping(_) => { + tracing::trace!("received Ping"); + } + server_msg => { + tracing::debug!("received unknown message {server_msg:?}"); + }, + } + }, + request = self.internal_receiver.select_next_some() => { + match request { + WebSocketRequest::SendMessage(message, sender) => { + tracing::debug!("Sending {message:?}"); + let msg = Message::Text(serde_json::Value::try_from(message).context("JsonSerializeError")?.to_string().into()); + internal.send_worker(msg.clone()).await?; + let _ = sender.send(()); + }, + WebSocketRequest::Reconnect(reason) => return Ok(reason), + }; + } + }; + } + } +} + +fn deprecation_message(headers: &HeaderMap) -> Option { + let dep_state = headers.get(DEPRECATION_STATE_HEADER_NAME)?.to_str().ok()?; + let msg = headers.get(DEPRECATION_MSG_HEADER_NAME)?.to_str().ok()?; + Some(format!("{dep_state}: {msg}")) +} + +impl WebSocketInternal { + async fn new( + ws_url: Url, + session_id: SessionId, + connection_count: u32, + last_close_reason: String, + max_observed_timestamp: Option, + client_id: &str, + ) -> anyhow::Result { + let mut request = (&ws_url).into_client_request().context("Bad WS Url")?; + request.headers_mut().insert( + "Convex-Client", + client_id.try_into().context("Bad client id")?, + ); + let (ws_stream, response) = connect_async(request).await.map_err(|e| { + if let tungstenite::Error::Http(ref response) = e { + let body = response + .body() + .as_deref() + .map(String::from_utf8_lossy) + .unwrap_or_default(); + return anyhow::anyhow!("Connection to {ws_url} failed: {e}: {body}"); + } + anyhow::anyhow!("Connection to {ws_url} failed: {e}") + })?; + + if let Some(msg) = deprecation_message(response.headers()) { + tracing::warn!("{msg}"); + } + + let last_server_response = Instant::now(); + let mut internal = WebSocketInternal { + ws_stream, + last_server_response, + }; + + // Send an initial connect message on the new websocket + let message = ClientMessage::Connect { + session_id, + connection_count, + last_close_reason, + max_observed_timestamp, + client_ts: Some(0), + }; + let msg = Message::Text( + serde_json::Value::try_from(message) + .context("JSONSerializationErrorOnConnect")? + .to_string() + .into(), + ); + internal.send_worker(msg).await?; + + Ok(internal) + } + + async fn send_worker(&mut self, message: Message) -> anyhow::Result<()> { + self.ws_stream + .send(message) + .await + .context("WebsocketClosedOnSend") + } +} diff --git a/third_party/convex_rs/src/value/export/mod.rs b/third_party/convex_rs/src/value/export/mod.rs new file mode 100644 index 00000000..8e43ede2 --- /dev/null +++ b/third_party/convex_rs/src/value/export/mod.rs @@ -0,0 +1,191 @@ +use serde_json::{ + json, + Value as JsonValue, +}; + +use crate::Value; + +#[cfg(any(test, feature = "testing"))] +pub mod roundtrip; + +impl Value { + /// Converts this value to a JSON value in the `json` export format. + /// + /// + /// It is possible for distinct Convex values to be serialized to the same + /// JSON value by this method. For instance, strings and binary values are + /// both exported as JSON strings. However, it is possible to convert the + /// exported value back to a unique Convex value if you also have the `Type` + /// value associated with the original Convex value (see `roundtrip.rs`). + /// + /// # Example + /// ``` + /// use convex::Value; + /// use serde_json::{ + /// json, + /// Value as JsonValue, + /// }; + /// + /// let value = Value::Bytes(vec![0b00000000, 0b00010000, 0b10000011]); + /// assert_eq!(JsonValue::from(value.clone()), json!({ "$bytes": "ABCD" })); + /// assert_eq!(value.export(), json!("ABCD")); + /// ``` + pub fn export(self) -> JsonValue { + match self { + Value::Null => JsonValue::Null, + Value::Int64(value) => JsonValue::String(value.to_string()), + Value::Float64(value) => { + if value.is_nan() { + json!("NaN") + } else if value.is_infinite() { + if value.is_sign_positive() { + json!("Infinity") + } else { + json!("-Infinity") + } + } else { + value.into() + } + }, + Value::Boolean(value) => JsonValue::Bool(value), + Value::String(value) => JsonValue::String(value), + Value::Bytes(value) => JsonValue::String(base64::encode(value)), + Value::Array(values) => { + JsonValue::Array(values.into_iter().map(|x| x.export()).collect()) + }, + Value::Object(map) => JsonValue::Object( + map.into_iter() + .map(|(key, value)| (key, value.export())) + .collect(), + ), + } + } +} + +#[cfg(test)] +mod tests { + use maplit::btreemap; + use serde_json::json; + + use super::*; + + #[test] + fn export_rustdoc_example() { + let value = Value::Bytes(vec![0b00000000, 0b00010000, 0b10000011]); + assert_eq!(JsonValue::from(value.clone()), json!({ "$bytes": "ABCD" })); + assert_eq!(value.export(), json!("ABCD")); + } + + #[test] + fn nulls_are_exported_as_null() { + assert_eq!(Value::Null.export(), JsonValue::Null) + } + + #[test] + fn booleans_are_exported_as_booleans() { + assert_eq!(Value::Boolean(true).export(), json!(true)); + assert_eq!(Value::Boolean(false).export(), json!(false)); + } + + #[test] + fn ints_are_exported_as_strings() { + assert_eq!(Value::Int64(1234).export(), json!("1234")); + + assert_eq!(Value::Int64(-314).export(), json!("-314")); + + assert_eq!(Value::Int64(0).export(), json!("0")); + + assert_eq!( + Value::Int64(i64::MIN).export(), + json!("-9223372036854775808") + ); + + assert_eq!( + Value::Int64(i64::MAX).export(), + json!("9223372036854775807") + ); + } + + #[test] + fn finite_floats_are_exported_as_numbers() { + assert_eq!(Value::Float64(12.34).export(), json!(12.34)); + } + + #[test] + fn pos_zero_is_exported_as_number() { + let json = Value::Float64(0.0).export(); + assert_eq!(json, json!(0.0)); + assert!(json.as_f64().unwrap().is_sign_positive()); + assert!(!json.as_f64().unwrap().is_sign_negative()); + } + + #[test] + fn neg_zero_is_exported_as_number() { + let json = Value::Float64(-0.0).export(); + assert_eq!(json, json!(-0.0)); + assert!(json.as_f64().unwrap().is_sign_negative()); + assert!(!json.as_f64().unwrap().is_sign_positive()); + } + + #[test] + fn infinite_floats_are_exported_as_strings() { + assert_eq!(Value::Float64(f64::INFINITY).export(), json!("Infinity")); + assert_eq!( + Value::Float64(f64::NEG_INFINITY).export(), + json!("-Infinity") + ); + } + + #[test] + fn nan_is_exported_as_string() { + assert_eq!(Value::Float64(f64::NAN).export(), json!("NaN")); + } + + #[test] + fn strings_are_exported_as_strings() { + assert_eq!(Value::Null.export(), JsonValue::Null); + } + + #[test] + fn bytes_are_exported_as_base64() { + let vec: Vec = vec![ + 0b00000000, 0b00010000, 0b10000011, 0b00010000, 0b01010001, 0b10000111, 0b00100000, + 0b10010010, 0b10001011, 0b00110000, 0b11010011, 0b10001111, 0b01000001, 0b00010100, + 0b10010011, 0b01010001, 0b01010101, 0b10010111, 0b01100001, 0b10010110, 0b10011011, + 0b01110001, 0b11010111, 0b10011111, 0b10000010, 0b00011000, 0b10100011, 0b10010010, + 0b01011001, 0b10100111, 0b10100010, 0b10011010, 0b10101011, 0b10110010, 0b11011011, + 0b10101111, 0b11000011, 0b00011100, 0b10110011, 0b11010011, 0b01011101, 0b10110111, + 0b11100011, 0b10011110, 0b10111011, 0b11110011, 0b11011111, 0b10111111, 0b00000000, + ]; + + assert_eq!( + Value::Bytes(vec).export(), + json!("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/AA==") + ); + } + + #[test] + fn arrays_are_exported_as_arrays() { + assert_eq!( + Value::Array(vec![Value::Int64(1), Value::Int64(2), Value::Int64(3)]).export(), + json!(["1", "2", "3"]), + ); + } + + #[test] + fn objects_are_exported_as_objects() { + assert_eq!( + Value::Object(btreemap! { + "a".to_string() => 1.into(), + "b".to_string() => 2.into(), + "c".to_string() => 3.into(), + }) + .export(), + json!({ + "a": "1", + "b": "2", + "c": "3", + }), + ); + } +} diff --git a/third_party/convex_rs/src/value/export/roundtrip.rs b/third_party/convex_rs/src/value/export/roundtrip.rs new file mode 100644 index 00000000..1ff1307f --- /dev/null +++ b/third_party/convex_rs/src/value/export/roundtrip.rs @@ -0,0 +1,169 @@ +use std::collections::BTreeMap; + +use anyhow::Context; +use serde_json::Value as JsonValue; + +use crate::Value; + +/// Type hint associated with a Convex value. This allows us to uniquely convert +/// the exported value back to the original Convex value. +#[allow(missing_docs)] +pub enum ExportContext { + Null, + Int64, + Float64 { + // Store the f64 value in the export context when it is NaN, because the export format + // assumes a single NaN value. This ensures that we can fully roundtrip values. + nan_value: Option, + }, + Boolean, + String, + Bytes, + Array(Vec), + Set, + Map, + Object(BTreeMap), +} + +impl ExportContext { + /// Returns the export context of a Convex value + pub fn of(value: &Value) -> ExportContext { + match value { + Value::Null => ExportContext::Null, + Value::Int64(_) => ExportContext::Int64, + Value::Float64(f) => ExportContext::Float64 { + nan_value: f.is_nan().then_some(*f), + }, + Value::Boolean(_) => ExportContext::Boolean, + Value::String(_) => ExportContext::String, + Value::Bytes(_) => ExportContext::Bytes, + Value::Array(elements) => { + ExportContext::Array(elements.iter().map(ExportContext::of).collect()) + }, + Value::Object(fields) => ExportContext::Object( + fields + .iter() + .map(|(key, value)| (key.clone(), ExportContext::of(value))) + .collect(), + ), + } + } +} + +impl TryFrom<(JsonValue, &ExportContext)> for Value { + type Error = anyhow::Error; + + fn try_from( + (exported_value, type_hint): (JsonValue, &ExportContext), + ) -> Result { + match type_hint { + ExportContext::Null => Ok(Value::Null), + ExportContext::Int64 => match exported_value { + JsonValue::String(str) => str + .parse::() + .map(Value::from) + .context("Unexpected string for i64"), + _ => anyhow::bail!("Unexpected value for i64"), + }, + ExportContext::Float64 { + nan_value: Some(nan_value), + } => { + if !nan_value.is_nan() { + anyhow::bail!("Unexpected non-NaN value in the export context"); + } + + if exported_value != JsonValue::String(String::from("NaN")) { + anyhow::bail!("Unexpected serialization of a NaN value"); + } + + Ok((*nan_value).into()) + }, + ExportContext::Float64 { nan_value: None } => match exported_value { + JsonValue::String(str) => match str.as_ref() { + "Infinity" => Ok(f64::INFINITY.into()), + "-Infinity" => Ok(f64::NEG_INFINITY.into()), + _ => anyhow::bail!("Unexpected string for f64"), + }, + JsonValue::Number(n) => n + .as_f64() + .map(Value::from) + .context("Unexpected number for i64"), + _ => anyhow::bail!("Unexpected value for f64"), + }, + ExportContext::Boolean => match exported_value { + JsonValue::Bool(value) => Ok(value.into()), + _ => anyhow::bail!("Unexpected value for boolean"), + }, + ExportContext::String => match exported_value { + JsonValue::String(value) => Ok(value.into()), + _ => anyhow::bail!("Unexpected value for string"), + }, + ExportContext::Bytes => match exported_value { + JsonValue::String(value) => base64::decode(value) + .map(Value::from) + .context("Unexpected string for bytes"), + _ => anyhow::bail!("Unexpected value for bytes"), + }, + ExportContext::Array(type_hints) => match exported_value { + JsonValue::Array(exported_values) => { + if exported_values.len() != type_hints.len() { + anyhow::bail!("Array lengths do not match"); + } + + let values: anyhow::Result> = exported_values + .into_iter() + .zip(type_hints) + .map(Value::try_from) + .collect(); + + Ok(Value::Array(values?)) + }, + _ => anyhow::bail!("Unexpected value for array"), + }, + ExportContext::Set | ExportContext::Map => Value::try_from(exported_value) + .context("Couldn’t deserialize set/map from internal representation"), + ExportContext::Object(type_hints) => match exported_value { + JsonValue::Object(exported_values) => { + let entries: anyhow::Result> = exported_values + .into_iter() + .map(|(key, value)| { + let Some(type_hint) = type_hints.get(&key) else { + anyhow::bail!("Missing export context for an object key"); + }; + Ok((key, (value, type_hint).try_into()?)) + }) + .collect(); + + Ok(Value::Object(entries?)) + }, + _ => anyhow::bail!("Unexpected value for object"), + }, + } + } +} + +#[cfg(test)] +mod tests { + use proptest::prelude::*; + + use crate::{ + value::export::roundtrip::ExportContext, + Value, + }; + + proptest! { + #![proptest_config(ProptestConfig { + failure_persistence: None, ..ProptestConfig::default() + })] + #[test] + fn export_roundtrips_with_type_hint(value in any::()) { + let exported_value = value.clone().export(); + let type_hint = ExportContext::of(&value); + + prop_assert_eq!( + value, + Value::try_from((exported_value, &type_hint)).unwrap() + ); + } + } +} diff --git a/third_party/convex_rs/src/value/json/bytes.rs b/third_party/convex_rs/src/value/json/bytes.rs new file mode 100644 index 00000000..45d4f899 --- /dev/null +++ b/third_party/convex_rs/src/value/json/bytes.rs @@ -0,0 +1,14 @@ +/// Helper functions for encoding `Bytes`s as `String`s. +pub enum JsonBytes {} + +impl JsonBytes { + /// Encode a binary string as a string. + pub fn encode(bytes: &Vec) -> String { + base64::encode(&bytes[..]) + } + + /// Decode a binary string from a string. + pub fn decode(s: String) -> anyhow::Result> { + Ok(base64::decode(s.as_bytes())?) + } +} diff --git a/third_party/convex_rs/src/value/json/float.rs b/third_party/convex_rs/src/value/json/float.rs new file mode 100644 index 00000000..6777eb20 --- /dev/null +++ b/third_party/convex_rs/src/value/json/float.rs @@ -0,0 +1,19 @@ +use anyhow::anyhow; + +/// Helper functions for encoding `f64`s as `String`s. +pub enum JsonFloat {} + +impl JsonFloat { + /// Encode an `f64` as a string. + pub fn encode(n: f64) -> String { + base64::encode(n.to_le_bytes()) + } + + /// Decode an `f64` from a string. + pub fn decode(s: String) -> anyhow::Result { + let bytes: [u8; 8] = base64::decode(s.as_bytes())? + .try_into() + .map_err(|_| anyhow!("Float64 must be exactly eight bytes"))?; + Ok(f64::from_le_bytes(bytes)) + } +} diff --git a/third_party/convex_rs/src/value/json/integer.rs b/third_party/convex_rs/src/value/json/integer.rs new file mode 100644 index 00000000..15f914c5 --- /dev/null +++ b/third_party/convex_rs/src/value/json/integer.rs @@ -0,0 +1,19 @@ +use anyhow::anyhow; + +/// Helper functions for encoding `Int64`s as `String`s. +pub enum JsonInteger {} + +impl JsonInteger { + /// Encode an integer as a string. + pub fn encode(n: i64) -> String { + base64::encode(n.to_le_bytes()) + } + + /// Decode an integer from a string. + pub fn decode(s: String) -> anyhow::Result { + let bytes: [u8; 8] = base64::decode(s.as_bytes())? + .try_into() + .map_err(|_| anyhow!("Int64 must be exactly eight bytes"))?; + Ok(i64::from_le_bytes(bytes)) + } +} diff --git a/third_party/convex_rs/src/value/json/mod.rs b/third_party/convex_rs/src/value/json/mod.rs new file mode 100644 index 00000000..18d8d589 --- /dev/null +++ b/third_party/convex_rs/src/value/json/mod.rs @@ -0,0 +1,157 @@ +use std::{ + cmp::Ordering, + collections::BTreeMap, + num::FpCategory, +}; + +use anyhow::Context; +use serde_json::{ + json, + Value as JsonValue, +}; + +use crate::value::Value; + +mod bytes; +mod float; +mod integer; + +/// Is a floating point number native zero? +fn is_negative_zero(n: f64) -> bool { + matches!(n.total_cmp(&-0.0), Ordering::Equal) +} + +impl From for JsonValue { + fn from(value: Value) -> JsonValue { + match value { + Value::Null => JsonValue::Null, + Value::Int64(n) => json!({ "$integer": integer::JsonInteger::encode(n) }), + Value::Float64(n) => { + let mut is_special = is_negative_zero(n); + is_special |= match n.classify() { + FpCategory::Zero | FpCategory::Normal | FpCategory::Subnormal => false, + FpCategory::Infinite | FpCategory::Nan => true, + }; + if is_special { + json!({ "$float": float::JsonFloat::encode(n) }) + } else { + json!(n) + } + }, + Value::Boolean(b) => json!(b), + Value::String(s) => json!(s), + Value::Bytes(b) => json!({ "$bytes": bytes::JsonBytes::encode(&b) }), + Value::Array(a) => JsonValue::from(a), + Value::Object(o) => o.into_iter().collect(), + } + } +} + +impl TryFrom for Value { + type Error = anyhow::Error; + + fn try_from(value: JsonValue) -> anyhow::Result { + let r = match value { + JsonValue::Null => Self::Null, + JsonValue::Bool(b) => Self::from(b), + JsonValue::Number(n) => { + // TODO: JSON supports arbitrary precision numbers? + let n = n + .as_f64() + .context("Arbitrary precision JSON integers unsupported")?; + Value::from(n) + }, + JsonValue::String(s) => Self::from(s), + JsonValue::Array(arr) => { + let mut out = Vec::with_capacity(arr.len()); + for a in arr { + out.push(Value::try_from(a)?); + } + Value::Array(out) + }, + JsonValue::Object(map) => { + if map.len() == 1 { + let (key, value) = map.into_iter().next().unwrap(); + match &key[..] { + "$bytes" => { + let i: String = serde_json::from_value(value)?; + Self::Bytes(bytes::JsonBytes::decode(i)?) + }, + "$integer" => { + let i: String = serde_json::from_value(value)?; + Self::from(integer::JsonInteger::decode(i)?) + }, + "$float" => { + let i: String = serde_json::from_value(value)?; + let n = float::JsonFloat::decode(i)?; + // Float64s encoded as a $float object must not fit into a regular + // `number`. + if !is_negative_zero(n) { + if let FpCategory::Normal | FpCategory::Subnormal = n.classify() { + anyhow::bail!("Float64 {} should be encoded as a number", n); + } + } + Self::from(n) + }, + "$set" => { + anyhow::bail!( + "Received a Set which is no longer supported as a Convex type, \ + with values: {value}" + ); + }, + "$map" => { + anyhow::bail!( + "Received a Map which is no longer supported as a Convex type, \ + with values: {value}" + ); + }, + _ => { + let mut fields = BTreeMap::new(); + fields.insert(key, Self::try_from(value)?); + Self::Object(fields) + }, + } + } else { + let mut fields = BTreeMap::new(); + for (key, value) in map { + fields.insert(key, Self::try_from(value)?); + } + Self::Object(fields) + } + }, + }; + Ok(r) + } +} + +#[cfg(test)] +mod tests { + use convex_sync_types::testing::assert_roundtrips; + use proptest::prelude::*; + use serde_json::Value as JsonValue; + + use crate::Value; + + proptest! { + #![proptest_config( + ProptestConfig { failure_persistence: None, ..ProptestConfig::default() } + )] + + #[test] + fn test_value_roundtrips(value in any::()) { + assert_roundtrips::(value); + } + } + + #[test] + fn test_value_roundtrips_trophies() { + let trophies = vec![ + Value::Float64(1.0), + Value::Float64(f64::NAN), + Value::Array(vec![Value::Float64(f64::NAN)]), + ]; + for trophy in trophies { + assert_roundtrips::(trophy); + } + } +} diff --git a/third_party/convex_rs/src/value/mod.rs b/third_party/convex_rs/src/value/mod.rs new file mode 100644 index 00000000..681e141d --- /dev/null +++ b/third_party/convex_rs/src/value/mod.rs @@ -0,0 +1,136 @@ +use std::collections::BTreeMap; + +pub mod export; +mod json; +mod sorting; +use thiserror::Error; + +/// A value that can be passed as an argument or returned from Convex functions. +/// They correspond to the [supported Convex types](https://docs.convex.dev/database/types). +#[derive(Clone, Debug)] +#[allow(missing_docs)] +pub enum Value { + Null, + Int64(i64), + Float64(f64), + Boolean(bool), + String(String), + Bytes(Vec), + Array(Vec), + Object(BTreeMap), +} + +impl> From> for Value { + fn from(v: Option) -> Value { + v.map(|v| v.into()).unwrap_or(Value::Null) + } +} + +impl From for Value { + fn from(v: i64) -> Value { + Value::Int64(v) + } +} + +impl From for Value { + fn from(v: f64) -> Value { + Value::Float64(v) + } +} + +impl From for Value { + fn from(v: bool) -> Value { + Value::Boolean(v) + } +} + +impl From<&str> for Value { + fn from(v: &str) -> Value { + Value::String(v.into()) + } +} + +impl From for Value { + fn from(v: String) -> Value { + Value::String(v) + } +} + +impl From> for Value { + fn from(v: Vec) -> Value { + Value::Bytes(v) + } +} + +impl From> for Value { + fn from(v: Vec) -> Value { + Value::Array(v) + } +} + +#[cfg(any(test, feature = "testing"))] +mod proptest { + use proptest::prelude::*; + + use super::Value; + + impl Arbitrary for Value { + type Parameters = (); + type Strategy = proptest::strategy::BoxedStrategy; + + fn arbitrary_with((): Self::Parameters) -> Self::Strategy { + value_strategy(4, 32, 8).boxed() + } + } + + fn value_strategy( + depth: usize, + node_target: usize, + branching: usize, + ) -> impl Strategy { + // https://altsysrq.github.io/proptest-book/proptest/tutorial/recursive.html + let leaf = prop_oneof![ + 1 => Just(Value::Null), + 1 => any::().prop_map(Value::from), + 1 => (prop::num::f64::ANY | prop::num::f64::SIGNALING_NAN).prop_map(Value::from), + 1 => any::().prop_map(Value::from), + 1 => any::().prop_map(Value::String), + 1 => any::>().prop_map(Value::Bytes), + ]; + leaf.prop_recursive( + depth as u32, + node_target as u32, + branching as u32, + move |inner| { + prop_oneof![ + // Manually create the strategies here rather than using the `Arbitrary` + // implementations on `Array`, etc. This lets us explicitly pass `inner` + // through rather than starting the `Value` strategy from + // scratch at each tree level. + prop::collection::vec(inner.clone(), 0..branching).prop_map(Value::Array), + prop::collection::btree_map(any::(), inner, 0..branching) + .prop_map(Value::Object), + ] + }, + ) + } +} + +/// An application error that can be returned from Convex functions. To learn +/// more about throwing custom application errors, see [Convex Errors](https://docs.convex.dev/functions/error-handling/application-errors#throwing-application-errors). +#[derive(Error, Clone, PartialEq, Eq)] +#[error("{:}", message)] +pub struct ConvexError { + /// From any error, redacted from prod deployments. + pub message: String, + /// Custom application error data payload that can be passed from your + /// function to a client. + pub data: Value, +} + +impl std::fmt::Debug for ConvexError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let message = &self.message; + write!(f, "{message:#?}") + } +} diff --git a/third_party/convex_rs/src/value/sorting.rs b/third_party/convex_rs/src/value/sorting.rs new file mode 100644 index 00000000..5990f205 --- /dev/null +++ b/third_party/convex_rs/src/value/sorting.rs @@ -0,0 +1,75 @@ +//! Implementation of `Ord` and `Eq` for `Value` that works around limitations +//! of f64 by using a `TotalOrdF64` type. + +use std::{ + cmp::Ordering, + collections::BTreeMap, +}; + +use crate::value::Value; + +#[derive(Eq, PartialEq, Ord, PartialOrd)] +enum OrdValue<'a> { + Null, + Int64(i64), + Float64(TotalOrdF64), + Boolean(bool), + String(&'a String), + Bytes(&'a Vec), + Array(&'a Vec), + Object(&'a BTreeMap), +} + +impl<'a> From<&'a Value> for OrdValue<'a> { + fn from(v: &'a Value) -> OrdValue<'a> { + match v { + Value::Null => OrdValue::Null, + Value::Int64(x) => OrdValue::Int64(*x), + Value::Float64(x) => OrdValue::Float64(TotalOrdF64(*x)), + Value::Boolean(x) => OrdValue::Boolean(*x), + Value::String(x) => OrdValue::String(x), + Value::Bytes(x) => OrdValue::Bytes(x), + Value::Array(x) => OrdValue::Array(x), + Value::Object(x) => OrdValue::Object(x), + } + } +} + +#[derive(Clone, Debug)] +struct TotalOrdF64(f64); + +impl Ord for TotalOrdF64 { + fn cmp(&self, other: &Self) -> Ordering { + self.0.total_cmp(&other.0) + } +} +impl PartialOrd for TotalOrdF64 { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl PartialEq for TotalOrdF64 { + fn eq(&self, other: &Self) -> bool { + matches!(self.cmp(other), Ordering::Equal) + } +} +impl Eq for TotalOrdF64 {} + +impl PartialEq for Value { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} +impl Eq for Value {} + +impl PartialOrd for Value { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Value { + fn cmp(&self, other: &Self) -> Ordering { + OrdValue::from(self).cmp(&OrdValue::from(other)) + } +} diff --git a/tool/audit_convex_contract.mjs b/tool/audit_convex_contract.mjs new file mode 100644 index 00000000..4e0362b5 --- /dev/null +++ b/tool/audit_convex_contract.mjs @@ -0,0 +1,174 @@ +import { readFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const toolDirectory = dirname(fileURLToPath(import.meta.url)); +const repositoryRoot = resolve(toolDirectory, ".."); +const functionSpec = JSON.parse( + readFileSync(resolve(repositoryRoot, "convex/function_spec.json"), "utf8"), +); +const snapshottedErrorCodes = JSON.parse( + readFileSync(resolve(repositoryRoot, "convex/error_codes.json"), "utf8"), +); +const errorsSource = readFileSync( + resolve(repositoryRoot, "convex/lib/errors.ts"), + "utf8", +); + +const failures = []; +const supportedValidatorTypes = new Set([ + "array", + "bigint", + "boolean", + "bytes", + "id", + "literal", + "null", + "number", + "object", + "record", + "string", + "union", +]); +const allowedPublicIds = new Set([ + "images.js:completeUpload.args.storageId:_storage", +]); + +function fail(message) { + failures.push(message); +} + +function auditValidator(validator, path, identifier) { + if (validator === null || typeof validator !== "object") { + fail(`${identifier}.${path.join(".")} is not a validator object`); + return; + } + if (!supportedValidatorTypes.has(validator.type)) { + fail( + `${identifier}.${path.join(".")} uses unsupported validator ${String(validator.type)}`, + ); + return; + } + + switch (validator.type) { + case "array": + auditValidator(validator.value, [...path, "item"], identifier); + break; + case "object": + for (const [fieldName, field] of Object.entries(validator.value)) { + if ( + field === null || + typeof field !== "object" || + typeof field.optional !== "boolean" || + field.fieldType === undefined + ) { + fail(`${identifier}.${[...path, fieldName].join(".")} has an invalid field`); + continue; + } + auditValidator(field.fieldType, [...path, fieldName], identifier); + } + break; + case "record": + auditValidator(validator.keys, [...path, "key"], identifier); + if ( + validator.values === null || + typeof validator.values !== "object" || + validator.values.optional !== false || + validator.values.fieldType === undefined + ) { + fail(`${identifier}.${path.join(".")} has invalid record values`); + } else { + auditValidator( + validator.values.fieldType, + [...path, "value"], + identifier, + ); + } + break; + case "union": + if (!Array.isArray(validator.value) || validator.value.length === 0) { + fail(`${identifier}.${path.join(".")} has an empty union`); + } else { + validator.value.forEach((member, index) => + auditValidator(member, [...path, `union${index}`], identifier), + ); + } + break; + case "id": { + const key = `${identifier}.${path.join(".")}:${validator.tableName}`; + if (!allowedPublicIds.has(key)) { + fail(`${identifier}.${path.join(".")} exposes Convex id ${validator.tableName}`); + } + break; + } + } +} + +if ( + functionSpec === null || + typeof functionSpec !== "object" || + !Array.isArray(functionSpec.functions) || + Object.keys(functionSpec).some((key) => key !== "functions") +) { + fail("function_spec.json must contain only a functions array"); +} else { + const identifiers = new Set(); + for (const functionSpecEntry of functionSpec.functions) { + if (identifiers.has(functionSpecEntry.identifier)) { + fail(`duplicate function identifier ${functionSpecEntry.identifier}`); + } + identifiers.add(functionSpecEntry.identifier); + if (functionSpecEntry.visibility?.kind !== "public") { + continue; + } + if (functionSpecEntry.args === null) { + fail(`${functionSpecEntry.identifier} is missing an args validator`); + } else { + auditValidator(functionSpecEntry.args, ["args"], functionSpecEntry.identifier); + } + if (functionSpecEntry.returns === null) { + fail(`${functionSpecEntry.identifier} is missing a return validator`); + } else { + auditValidator( + functionSpecEntry.returns, + ["returns"], + functionSpecEntry.identifier, + ); + } + } +} + +const errorCodesDeclaration = errorsSource.match( + /export const errorCodes\s*=\s*\[([\s\S]*?)\]\s*as const/, +); +if (errorCodesDeclaration === null) { + fail("convex/lib/errors.ts does not export errorCodes as a const array"); +} else { + const sourceCodes = [ + ...errorCodesDeclaration[1].matchAll(/"([A-Z][A-Z0-9_]*)"/g), + ].map((match) => match[1]); + const sortedSourceCodes = [...new Set(sourceCodes)].sort(); + if (sourceCodes.length !== sortedSourceCodes.length) { + fail("errorCodes contains duplicate values"); + } + if (JSON.stringify(sourceCodes) !== JSON.stringify(sortedSourceCodes)) { + fail("errorCodes must be sorted"); + } + if ( + JSON.stringify(snapshottedErrorCodes) !== JSON.stringify(sortedSourceCodes) + ) { + fail("error_codes.json does not match convex/lib/errors.ts"); + } +} + +if (failures.length > 0) { + console.error(failures.map((failure) => `- ${failure}`).join("\n")); + process.exitCode = 1; +} else { + const publicFunctionCount = functionSpec.functions.filter( + (entry) => entry.visibility?.kind === "public", + ).length; + console.log( + `Convex contract audit passed: ${publicFunctionCount} public functions, ${snapshottedErrorCodes.length} error codes.`, + ); +} diff --git a/tool/convex_client_gauntlet/.gitignore b/tool/convex_client_gauntlet/.gitignore new file mode 100644 index 00000000..c8bcc777 --- /dev/null +++ b/tool/convex_client_gauntlet/.gitignore @@ -0,0 +1,4 @@ +/.dart_tool/ +/lib/_probe_caller.dart +/lib/_probe_generated/ +/runtime/build/ diff --git a/tool/convex_client_gauntlet/README.md b/tool/convex_client_gauntlet/README.md new file mode 100644 index 00000000..6bb65af5 --- /dev/null +++ b/tool/convex_client_gauntlet/README.md @@ -0,0 +1,20 @@ +# Convex Dart client gauntlet + +This isolated Dart package reproduces the compile-time contract gate declared +for Icarus's Convex client comparison. It pins `dartvex` and +`dartvex_codegen` to 0.2.0 without adding either package to the application. + +Run the repaired compile-time contract gate and its regression test from this +directory: + +```bash +fvm dart pub get +fvm dart run bin/run.dart +fvm dart test +``` + +The gate uses an explicit return schema and an Icarus-owned strict wrapper. The +wrapper rejects Dartvex warnings and public methods that degrade to a +`dynamic` result. The result-rename fixture changes only `publicId` to +`folderPublicId`, so the unchanged caller proves the generated return type at +analysis time. diff --git a/tool/convex_client_gauntlet/analysis_options.yaml b/tool/convex_client_gauntlet/analysis_options.yaml new file mode 100644 index 00000000..84879e80 --- /dev/null +++ b/tool/convex_client_gauntlet/analysis_options.yaml @@ -0,0 +1,4 @@ +analyzer: + exclude: + - lib/_probe_caller.dart + - lib/_probe_generated/** diff --git a/tool/convex_client_gauntlet/bin/run.dart b/tool/convex_client_gauntlet/bin/run.dart new file mode 100644 index 00000000..399e637c --- /dev/null +++ b/tool/convex_client_gauntlet/bin/run.dart @@ -0,0 +1,16 @@ +import 'dart:io'; + +import 'package:icarus_convex_client_gauntlet/contract_gate.dart'; + +Future main(List args) async { + final outputIndex = args.indexOf('--output'); + final outputPath = outputIndex == -1 || outputIndex + 1 >= args.length + ? null + : args[outputIndex + 1]; + final result = await evaluateContractGate(); + final json = '${result.toPrettyJson()}\n'; + stdout.write(json); + if (outputPath != null) { + File(outputPath).writeAsStringSync(json); + } +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_argument_renamed.json b/tool/convex_client_gauntlet/fixtures/folders_argument_renamed.json new file mode 100644 index 00000000..8acd8977 --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_argument_renamed.json @@ -0,0 +1,50 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { + "type": "object", + "value": { + "parentPublicId": { + "fieldType": { "type": "string" }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { "type": "literal", "value": "owned" }, + { "type": "literal", "value": "shared" }, + { "type": "literal", "value": "all" } + ] + }, + "optional": true + } + } + }, + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "publicId": { "fieldType": { "type": "string" }, "optional": false }, + "name": { "fieldType": { "type": "string" }, "optional": false }, + "iconId": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconCodePoint": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconFontFamily": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "iconFontPackage": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "color": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "customColorValue": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "parentFolderPublicId": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "createdAt": { "fieldType": { "type": "number" }, "optional": false }, + "updatedAt": { "fieldType": { "type": "number" }, "optional": false }, + "role": { "fieldType": { "type": "union", "value": [{ "type": "literal", "value": "owner" }, { "type": "literal", "value": "editor" }, { "type": "literal", "value": "viewer" }] }, "optional": false } + } + } + }, + "identifier": "folders.js:listForParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_function_renamed.json b/tool/convex_client_gauntlet/fixtures/folders_function_renamed.json new file mode 100644 index 00000000..f1f04273 --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_function_renamed.json @@ -0,0 +1,50 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { + "type": "object", + "value": { + "parentFolderPublicId": { + "fieldType": { "type": "string" }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { "type": "literal", "value": "owned" }, + { "type": "literal", "value": "shared" }, + { "type": "literal", "value": "all" } + ] + }, + "optional": true + } + } + }, + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "publicId": { "fieldType": { "type": "string" }, "optional": false }, + "name": { "fieldType": { "type": "string" }, "optional": false }, + "iconId": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconCodePoint": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconFontFamily": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "iconFontPackage": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "color": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "customColorValue": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "parentFolderPublicId": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "createdAt": { "fieldType": { "type": "number" }, "optional": false }, + "updatedAt": { "fieldType": { "type": "number" }, "optional": false }, + "role": { "fieldType": { "type": "union", "value": [{ "type": "literal", "value": "owner" }, { "type": "literal", "value": "editor" }, { "type": "literal", "value": "viewer" }] }, "optional": false } + } + } + }, + "identifier": "folders.js:listWithinParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_list_for_parent.json b/tool/convex_client_gauntlet/fixtures/folders_list_for_parent.json new file mode 100644 index 00000000..d424a633 --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_list_for_parent.json @@ -0,0 +1,50 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { + "type": "object", + "value": { + "parentFolderPublicId": { + "fieldType": { "type": "string" }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { "type": "literal", "value": "owned" }, + { "type": "literal", "value": "shared" }, + { "type": "literal", "value": "all" } + ] + }, + "optional": true + } + } + }, + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "publicId": { "fieldType": { "type": "string" }, "optional": false }, + "name": { "fieldType": { "type": "string" }, "optional": false }, + "iconId": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconCodePoint": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconFontFamily": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "iconFontPackage": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "color": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "customColorValue": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "parentFolderPublicId": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "createdAt": { "fieldType": { "type": "number" }, "optional": false }, + "updatedAt": { "fieldType": { "type": "number" }, "optional": false }, + "role": { "fieldType": { "type": "union", "value": [{ "type": "literal", "value": "owner" }, { "type": "literal", "value": "editor" }, { "type": "literal", "value": "viewer" }] }, "optional": false } + } + } + }, + "identifier": "folders.js:listForParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_missing_return.json b/tool/convex_client_gauntlet/fixtures/folders_missing_return.json new file mode 100644 index 00000000..7099ebf5 --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_missing_return.json @@ -0,0 +1,31 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { + "type": "object", + "value": { + "parentFolderPublicId": { + "fieldType": { "type": "string" }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { "type": "literal", "value": "owned" }, + { "type": "literal", "value": "shared" }, + { "type": "literal", "value": "all" } + ] + }, + "optional": true + } + } + }, + "returns": null, + "identifier": "folders.js:listForParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_result_renamed.json b/tool/convex_client_gauntlet/fixtures/folders_result_renamed.json new file mode 100644 index 00000000..f9656a4d --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_result_renamed.json @@ -0,0 +1,50 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { + "type": "object", + "value": { + "parentFolderPublicId": { + "fieldType": { "type": "string" }, + "optional": true + }, + "scope": { + "fieldType": { + "type": "union", + "value": [ + { "type": "literal", "value": "owned" }, + { "type": "literal", "value": "shared" }, + { "type": "literal", "value": "all" } + ] + }, + "optional": true + } + } + }, + "returns": { + "type": "array", + "value": { + "type": "object", + "value": { + "folderPublicId": { "fieldType": { "type": "string" }, "optional": false }, + "name": { "fieldType": { "type": "string" }, "optional": false }, + "iconId": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconCodePoint": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "iconFontFamily": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "iconFontPackage": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "color": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "customColorValue": { "fieldType": { "type": "union", "value": [{ "type": "number" }, { "type": "null" }] }, "optional": false }, + "parentFolderPublicId": { "fieldType": { "type": "union", "value": [{ "type": "string" }, { "type": "null" }] }, "optional": false }, + "createdAt": { "fieldType": { "type": "number" }, "optional": false }, + "updatedAt": { "fieldType": { "type": "number" }, "optional": false }, + "role": { "fieldType": { "type": "union", "value": [{ "type": "literal", "value": "owner" }, { "type": "literal", "value": "editor" }, { "type": "literal", "value": "viewer" }] }, "optional": false } + } + } + }, + "identifier": "folders.js:listForParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/fixtures/folders_unsupported_validator.json b/tool/convex_client_gauntlet/fixtures/folders_unsupported_validator.json new file mode 100644 index 00000000..56f3b87a --- /dev/null +++ b/tool/convex_client_gauntlet/fixtures/folders_unsupported_validator.json @@ -0,0 +1,20 @@ +{ + "url": "https://your-deployment.convex.cloud", + "functions": [ + { + "functionType": "Query", + "args": { "type": "object", "value": {} }, + "returns": { + "type": "object", + "value": { + "futureField": { + "fieldType": { "type": "future-validator" }, + "optional": false + } + } + }, + "identifier": "folders.js:listForParent", + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/convex_client_gauntlet/lib/contract_gate.dart b/tool/convex_client_gauntlet/lib/contract_gate.dart new file mode 100644 index 00000000..836296a6 --- /dev/null +++ b/tool/convex_client_gauntlet/lib/contract_gate.dart @@ -0,0 +1,341 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:crypto/crypto.dart'; +import 'strict_codegen.dart'; + +const _dartvexVersion = '0.2.0'; +const _dartvexCodegenVersion = '0.2.0'; + +final class ContractGateResult { + ContractGateResult({required this.report}); + + final Map report; + + String toPrettyJson() => const JsonEncoder.withIndent(' ').convert(report); +} + +Future evaluateContractGate({String? packageRoot}) async { + final root = Directory(packageRoot ?? Directory.current.path).absolute; + final generated = Directory('${root.path}/lib/_probe_generated'); + final caller = File('${root.path}/lib/_probe_caller.dart'); + + Future generate(String fixtureName) async { + return runStrictConvexCodegen( + specFile: '${root.path}/fixtures/$fixtureName', + outputDirectory: generated, + stableModulePaths: const ['modules/folders.dart'], + ); + } + + Future<_Analysis> analyzeCaller() async { + final process = await Process.run(Platform.resolvedExecutable, [ + 'analyze', + caller.path, + generated.path, + ], workingDirectory: root.path); + return _Analysis( + exitCode: process.exitCode, + output: '${process.stdout}${process.stderr}'.trim(), + ); + } + + try { + if (generated.existsSync()) { + generated.deleteSync(recursive: true); + } + if (caller.existsSync()) { + caller.deleteSync(); + } + + final baselineGeneration = await generate('folders_list_for_parent.json'); + caller.writeAsStringSync(_oldCaller); + final baselineAnalysis = await analyzeCaller(); + final baselineSource = File( + '${generated.path}/modules/folders.dart', + ).readAsStringSync(); + final resultIsDynamic = baselineSource.contains( + 'Future listForParent', + ); + final firstGeneration = _directorySnapshot(generated); + final firstGenerationHash = _snapshotSha256(firstGeneration); + + final secondGenerationResult = await generate( + 'folders_list_for_parent.json', + ); + final secondGeneration = _directorySnapshot(generated); + final secondGenerationHash = _snapshotSha256(secondGeneration); + final deterministic = + secondGenerationResult.exitCode == 0 && + _snapshotsEqual(firstGeneration, secondGeneration); + + final functionRenameGeneration = await generate( + 'folders_function_renamed.json', + ); + final functionRenameAnalysis = await analyzeCaller(); + + final argumentRenameGeneration = await generate( + 'folders_argument_renamed.json', + ); + final argumentRenameAnalysis = await analyzeCaller(); + + await generate('folders_list_for_parent.json'); + final resultRenameGeneration = await generate( + 'folders_result_renamed.json', + ); + final resultFieldAnalysis = await analyzeCaller(); + + final missingReturnGeneration = await generate( + 'folders_missing_return.json', + ); + + final unsupportedGeneration = await generate( + 'folders_unsupported_validator.json', + ); + final unsupportedSource = File( + '${generated.path}/modules/folders.dart', + ).readAsStringSync(); + + final functionRenameCaught = + functionRenameGeneration.accepted && + functionRenameAnalysis.exitCode != 0; + final argumentRenameCaught = + argumentRenameGeneration.accepted && + argumentRenameAnalysis.exitCode != 0; + final resultRenameCaught = + resultRenameGeneration.accepted && + !resultIsDynamic && + resultFieldAnalysis.exitCode != 0; + final missingReturnRejected = !missingReturnGeneration.accepted; + final unsupportedRejected = !unsupportedGeneration.accepted; + final baselineCompiles = + baselineGeneration.accepted && baselineAnalysis.exitCode == 0; + final gatePassed = + baselineCompiles && + functionRenameCaught && + argumentRenameCaught && + resultRenameCaught && + missingReturnRejected && + unsupportedRejected && + deterministic; + + final report = { + 'schemaVersion': 2, + 'evaluation': 'convex_dart_client_contract_gate', + 'baseCommit': _gitBaseCommit(root), + 'adapterCandidate': 'dartvex', + 'sdkVersion': _dartvexVersion, + 'codegenVersion': _dartvexCodegenVersion, + 'platform': _platformName(), + 'dartVersion': Platform.version, + 'fixture': 'folders:listForParent', + 'baselineCompiles': baselineCompiles, + 'baselineAnalysisExitCode': baselineAnalysis.exitCode, + 'baselineAnalysisDiagnostics': _sanitizeDiagnostics([ + baselineAnalysis.output, + ], root), + 'checks': >[ + { + 'id': 'function_rename', + 'required': 'old_generated_method_fails_analysis', + 'status': functionRenameCaught ? 'pass' : 'fail', + 'generationExitCode': functionRenameGeneration.exitCode, + 'rawGenerationExitCode': functionRenameGeneration.rawExitCode, + 'analysisExitCode': functionRenameAnalysis.exitCode, + }, + { + 'id': 'argument_rename', + 'required': 'old_named_argument_fails_analysis', + 'status': argumentRenameCaught ? 'pass' : 'fail', + 'generationExitCode': argumentRenameGeneration.exitCode, + 'rawGenerationExitCode': argumentRenameGeneration.rawExitCode, + 'analysisExitCode': argumentRenameAnalysis.exitCode, + }, + { + 'id': 'result_field_rename', + 'required': 'old_result_field_fails_analysis', + 'status': resultRenameCaught ? 'pass' : 'fail', + 'generationExitCode': resultRenameGeneration.exitCode, + 'rawGenerationExitCode': resultRenameGeneration.rawExitCode, + 'analysisExitCode': resultFieldAnalysis.exitCode, + 'generatedReturnType': resultIsDynamic ? 'dynamic' : 'typed', + 'detail': resultIsDynamic + ? 'The generated result is unexpectedly dynamic.' + : 'The explicit return is typed and the renamed result rejects the unchanged caller.', + }, + { + 'id': 'missing_return_schema', + 'required': 'strict_generation_rejects_public_dynamic_result', + 'status': missingReturnRejected ? 'pass' : 'fail', + 'generationExitCode': missingReturnGeneration.exitCode, + 'rawGenerationExitCode': missingReturnGeneration.rawExitCode, + 'diagnostics': _sanitizeDiagnostics( + missingReturnGeneration.diagnostics, + root, + ), + }, + { + 'id': 'unsupported_validator', + 'required': 'generation_stops_with_function_and_field_path', + 'status': unsupportedRejected ? 'pass' : 'fail', + 'generationExitCode': unsupportedGeneration.exitCode, + 'rawGenerationExitCode': unsupportedGeneration.rawExitCode, + 'generatedFieldType': + unsupportedSource.contains('dynamic futureField') + ? 'dynamic' + : 'not_dynamic', + 'diagnostics': _sanitizeDiagnostics( + unsupportedGeneration.diagnostics, + root, + ), + }, + { + 'id': 'deterministic_regeneration', + 'required': 'second_generation_has_no_diff', + 'status': deterministic ? 'pass' : 'fail', + 'firstSha256': firstGenerationHash, + 'secondSha256': secondGenerationHash, + 'changedFiles': _changedFiles(firstGeneration, secondGeneration), + }, + ], + 'gatePassed': gatePassed, + 'decision': gatePassed + ? 'continue_runtime_gauntlet' + : 'keep_convex_flutter', + 'runtimeGauntlet': { + 'status': gatePassed ? 'pending' : 'skipped', + 'reason': gatePassed ? null : 'compile_time_contract_gate_failed', + 'correctnessSeedsRun': 0, + 'operationsRun': 0, + 'pairedProfileRuns': 0, + 'p95RemoteConvergenceMs': null, + 'peakMemoryBytes': null, + }, + }; + return ContractGateResult(report: report); + } finally { + if (generated.existsSync()) { + generated.deleteSync(recursive: true); + } + if (caller.existsSync()) { + caller.deleteSync(); + } + } +} + +String _gitBaseCommit(Directory root) { + final result = Process.runSync('git', [ + 'merge-base', + 'HEAD', + 'origin/icarus-cloud', + ], workingDirectory: root.path); + if (result.exitCode != 0) { + throw StateError('Unable to resolve the Icarus base commit.'); + } + return result.stdout.toString().trim(); +} + +String _platformName() { + final match = RegExp(r'on "([^"]+)"').firstMatch(Platform.version); + final rawArchitecture = match?.group(1) ?? 'unknown'; + final architecture = rawArchitecture.replaceFirst( + '${Platform.operatingSystem}_', + '', + ); + return '${Platform.operatingSystem}-$architecture'; +} + +List _sanitizeDiagnostics(List diagnostics, Directory root) => + diagnostics + .map((message) => message.replaceAll(root.path, '')) + .toList(growable: false); + +Map> _directorySnapshot(Directory directory) { + final snapshot = >{}; + final files = + directory + .listSync(recursive: true) + .whereType() + .toList(growable: false) + ..sort((left, right) => left.path.compareTo(right.path)); + for (final file in files) { + final relative = file.path.substring(directory.path.length + 1); + snapshot[relative] = file.readAsBytesSync(); + } + return snapshot; +} + +bool _snapshotsEqual( + Map> left, + Map> right, +) { + if (left.length != right.length) { + return false; + } + for (final entry in left.entries) { + final other = right[entry.key]; + if (other == null || !_bytesEqual(entry.value, other)) { + return false; + } + } + return true; +} + +List _changedFiles( + Map> left, + Map> right, +) { + final paths = {...left.keys, ...right.keys}.toList()..sort(); + return paths + .where((path) { + final leftBytes = left[path]; + final rightBytes = right[path]; + return leftBytes == null || + rightBytes == null || + !_bytesEqual(leftBytes, rightBytes); + }) + .toList(growable: false); +} + +String _snapshotSha256(Map> snapshot) { + final bytes = BytesBuilder(copy: false); + for (final entry in snapshot.entries) { + bytes + ..add(utf8.encode(entry.key)) + ..addByte(0) + ..add(entry.value) + ..addByte(0); + } + return sha256.convert(bytes.takeBytes()).toString(); +} + +bool _bytesEqual(List left, List right) { + if (left.length != right.length) { + return false; + } + for (var index = 0; index < left.length; index += 1) { + if (left[index] != right[index]) { + return false; + } + } + return true; +} + +final class _Analysis { + const _Analysis({required this.exitCode, required this.output}); + + final int exitCode; + final String output; +} + +const _oldCaller = ''' +import '_probe_generated/api.dart'; + +Future readFirstFolderPublicId(ConvexApi api) async { + final result = await api.folders.listForParent( + parentFolderPublicId: const Optional.of('parent-folder'), + ); + return result.first.publicId; +} +'''; diff --git a/tool/convex_client_gauntlet/lib/strict_codegen.dart b/tool/convex_client_gauntlet/lib/strict_codegen.dart new file mode 100644 index 00000000..26774b4f --- /dev/null +++ b/tool/convex_client_gauntlet/lib/strict_codegen.dart @@ -0,0 +1,76 @@ +import 'dart:io'; + +import 'package:dartvex_codegen/dartvex_codegen.dart'; + +final class StrictGenerationResult { + const StrictGenerationResult({ + required this.rawExitCode, + required this.exitCode, + required this.logs, + required this.errors, + required this.diagnostics, + }); + + final int rawExitCode; + final int exitCode; + final List logs; + final List errors; + final List diagnostics; + + bool get accepted => exitCode == 0; +} + +Future runStrictConvexCodegen({ + required String specFile, + required Directory outputDirectory, + required Iterable stableModulePaths, +}) async { + final logs = []; + final errors = []; + final rawExitCode = await runConvexCodegen( + [ + 'generate', + '--spec-file', + specFile, + '--output', + outputDirectory.path, + ], + log: logs.add, + errorLog: errors.add, + ); + + final diagnostics = []; + if (rawExitCode != 0) { + diagnostics.add('Dartvex generation exited $rawExitCode.'); + } + diagnostics.addAll( + [...logs, ...errors].where((line) => line.contains('Warning:')), + ); + + if (rawExitCode == 0) { + for (final modulePath in stableModulePaths) { + final module = File('${outputDirectory.path}/$modulePath'); + if (!module.existsSync()) { + diagnostics.add('Stable generated module is missing: $modulePath'); + continue; + } + final source = module.readAsStringSync(); + for (final match in RegExp( + r'(?:Future|Stream)\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*\(', + ).allMatches(source)) { + diagnostics.add( + '$modulePath: public method ${match.group(1)} has an unexpected ' + 'dynamic result.', + ); + } + } + } + + return StrictGenerationResult( + rawExitCode: rawExitCode, + exitCode: diagnostics.isEmpty ? 0 : (rawExitCode == 0 ? 2 : rawExitCode), + logs: List.unmodifiable(logs), + errors: List.unmodifiable(errors), + diagnostics: List.unmodifiable(diagnostics), + ); +} diff --git a/tool/convex_client_gauntlet/pubspec.lock b/tool/convex_client_gauntlet/pubspec.lock new file mode 100644 index 00000000..5cfff0ae --- /dev/null +++ b/tool/convex_client_gauntlet/pubspec.lock @@ -0,0 +1,437 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d + url: "https://pub.dev" + source: hosted + version: "91.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + sha256: f51c8499b35f9b26820cfe914828a6a98a94efd5cc78b37bb7d03debae3a1d08 + url: "https://pub.dev" + source: hosted + version: "8.4.1" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: transitive + description: + name: dart_style + sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b + url: "https://pub.dev" + source: hosted + version: "3.1.3" + dartvex: + dependency: "direct main" + description: + name: dartvex + sha256: "7a343c5853f25a1a136051d2d37002a0e1e3f6c230b6f24560797880de33b5d8" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + dartvex_codegen: + dependency: "direct main" + description: + name: dartvex_codegen + sha256: "07f81e4b16460eeb58673f6e514911df92ba5b38513d9207a4211a92646512a7" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de + url: "https://pub.dev" + source: hosted + version: "2.1.3" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b + url: "https://pub.dev" + source: hosted + version: "1.0.5" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" + url: "https://pub.dev" + source: hosted + version: "1.19.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.dev" + source: hosted + version: "1.5.2" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "14c2945847669b44089bb1222f66873d7ff7103c58911917f2a63c5a62327898" + url: "https://pub.dev" + source: hosted + version: "0.10.14" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce + url: "https://pub.dev" + source: hosted + version: "3.1.3" +sdks: + dart: ">=3.11.0 <4.0.0" diff --git a/tool/convex_client_gauntlet/pubspec.yaml b/tool/convex_client_gauntlet/pubspec.yaml new file mode 100644 index 00000000..bd8a17d1 --- /dev/null +++ b/tool/convex_client_gauntlet/pubspec.yaml @@ -0,0 +1,13 @@ +name: icarus_convex_client_gauntlet +publish_to: none + +environment: + sdk: ">=3.11.0 <4.0.0" + +dependencies: + crypto: 3.0.7 + dartvex: 0.2.0 + dartvex_codegen: 0.2.0 + +dev_dependencies: + test: 1.26.3 diff --git a/tool/convex_client_gauntlet/results/contract_gate.json b/tool/convex_client_gauntlet/results/contract_gate.json new file mode 100644 index 00000000..6371db1c --- /dev/null +++ b/tool/convex_client_gauntlet/results/contract_gate.json @@ -0,0 +1,84 @@ +{ + "schemaVersion": 2, + "evaluation": "convex_dart_client_contract_gate", + "baseCommit": "e59402eedee9035cf14693fbd26fe8b097d6abfa", + "adapterCandidate": "dartvex", + "sdkVersion": "0.2.0", + "codegenVersion": "0.2.0", + "platform": "macos-arm64", + "dartVersion": "3.11.0 (stable) (Mon Feb 9 00:38:07 2026 -0800) on \"macos_arm64\"", + "fixture": "folders:listForParent", + "baselineCompiles": true, + "baselineAnalysisExitCode": 0, + "baselineAnalysisDiagnostics": [ + "Analyzing _probe_caller.dart, _probe_generated...\nNo issues found!" + ], + "checks": [ + { + "id": "function_rename", + "required": "old_generated_method_fails_analysis", + "status": "pass", + "generationExitCode": 0, + "rawGenerationExitCode": 0, + "analysisExitCode": 3 + }, + { + "id": "argument_rename", + "required": "old_named_argument_fails_analysis", + "status": "pass", + "generationExitCode": 0, + "rawGenerationExitCode": 0, + "analysisExitCode": 3 + }, + { + "id": "result_field_rename", + "required": "old_result_field_fails_analysis", + "status": "pass", + "generationExitCode": 0, + "rawGenerationExitCode": 0, + "analysisExitCode": 3, + "generatedReturnType": "typed", + "detail": "The explicit return is typed and the renamed result rejects the unchanged caller." + }, + { + "id": "missing_return_schema", + "required": "strict_generation_rejects_public_dynamic_result", + "status": "pass", + "generationExitCode": 2, + "rawGenerationExitCode": 0, + "diagnostics": [ + "modules/folders.dart: public method listForParent has an unexpected dynamic result." + ] + }, + { + "id": "unsupported_validator", + "required": "generation_stops_with_function_and_field_path", + "status": "pass", + "generationExitCode": 2, + "rawGenerationExitCode": 0, + "generatedFieldType": "dynamic", + "diagnostics": [ + "Warning: folders.js:listForParent → returns → field \"futureField\": Unknown Convex type \"future-validator\"; generated as dynamic." + ] + }, + { + "id": "deterministic_regeneration", + "required": "second_generation_has_no_diff", + "status": "pass", + "firstSha256": "ea6a3655ead9e0250282424593fa1be36907c55d7de8386bb5503d80b985bc36", + "secondSha256": "ea6a3655ead9e0250282424593fa1be36907c55d7de8386bb5503d80b985bc36", + "changedFiles": [] + } + ], + "gatePassed": true, + "decision": "continue_runtime_gauntlet", + "runtimeGauntlet": { + "status": "pending", + "reason": null, + "correctnessSeedsRun": 0, + "operationsRun": 0, + "pairedProfileRuns": 0, + "p95RemoteConvergenceMs": null, + "peakMemoryBytes": null + } +} diff --git a/tool/convex_client_gauntlet/runtime/README.md b/tool/convex_client_gauntlet/runtime/README.md new file mode 100644 index 00000000..6f3334cf --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/README.md @@ -0,0 +1,121 @@ +# Convex Dart client runtime gauntlet + +This package runs the same deterministic Icarus cloud workload through Dartvex +0.2.0 and the Icarus-repaired `convex_flutter` 3.0.1 package. It targets an +isolated local Convex deployment and uses a disposable Supabase user with the +public anon key. Never use or pass a `service_role` key. + +Each correctness candidate is configured for 50 seeds of 1,000 operations. The +trace includes offline queuing, delay, duplicate delivery, subscription restart, +rejected-token refresh, reconnect, revision conflicts, delete/recreate cycles, +and a persisted mid-run process checkpoint. A fresh Dartvex client performs the +canonical final-state and `.ica` round-trip verification. + +## Verify and build + +```sh +fvm dart format --output=none --set-exit-if-changed lib app/lib test tool +fvm dart analyze +fvm flutter test test/workload_test.dart +cd app +fvm flutter analyze +fvm flutter build macos --debug +``` + +The nested macOS app is required because `convex_flutter` loads its native Rust +bridge from the application bundle. Flutter's unit-test process cannot supply +that framework. + +## Run correctness + +Start an isolated local deployment from the repository root: + +```sh +npx convex dev --codegen disable --tail-logs disable +``` + +Set `SUPABASE_URL`, `SUPABASE_KEY`, `TEST_EMAIL`, and `TEST_PASSWORD` in the +environment. `SUPABASE_KEY` must be the public anon key. Then, from `app/`, run +each candidate with the same settings: + +```sh +export CONVEX_URL=http://127.0.0.1:3210 +export ADAPTER=dartvex +export SEED_COUNT=50 +export ALLOW_CHECKPOINT=1 +export RESET_PROGRESS=1 +export REPORT_NAME=icarus-dartvex-runtime-correctness +export GIT_COMMIT=$(git -C ../../../.. rev-parse HEAD) +build/macos/Build/Products/Debug/icarus_convex_runtime_runner.app/Contents/MacOS/icarus_convex_runtime_runner +``` + +When the runner reports `checkpoint`, run the same command again with +`RESET_PROGRESS=0`. Before switching adapters, replace the isolated deployment +data with the empty fixture, change `ADAPTER` and `REPORT_NAME`, and restore +`RESET_PROGRESS=1`: + +```sh +export CONVEX_DEPLOYMENT= +export CONVEX_SELF_HOSTED_URL=http://127.0.0.1:3210 +export CONVEX_SELF_HOSTED_ADMIN_KEY=$(jq -r .adminKey ../../../../.convex/local/default/config.json) +npx convex import --replace-all --table users ../../../../tool/convex_client_gauntlet/runtime/fixtures/empty.json -y +``` + +Correctness reports are written to the app container's temporary directory and +copied verbatim into `results/` after checking that they contain no credentials. +Phase 4 profiling is forbidden unless both candidates pass all correctness +conditions. + +## Auth and reconnect repair + +The local package at `third_party/convex_flutter` delegates refresh to the +Convex Rust state machine, protects replacement auth handles with a generation, +and exposes a real WebSocket reconnect. It uses the local Convex Rust 0.10.4 +crate at `third_party/convex_rs`, whose patch coordinates auth retry across the +client and WebSocket workers while preserving in-flight mutation replay. Read +both `ICARUS_PATCH.md` files before changing this boundary. + +Generated Flutter Rust Bridge files were produced with the pinned 2.11.1 +generator and must not be edited by hand: + +```sh +cd third_party/convex_flutter/rust +flutter_rust_bridge_codegen generate +``` + +## Run paired profile trials + +Only profile after both correctness artifacts pass. Build the native runner in +profile mode, choose an empty temporary output directory, and use the same +disposable public-client environment as correctness: + +```sh +cd tool/convex_client_gauntlet/runtime/app +fvm flutter build macos --profile +cd ../../../.. + +export PROFILE_OUTPUT_DIR=$(mktemp -d /tmp/icarus-convex-profile.XXXXXX) +export CONVEX_URL=http://127.0.0.1:3210 +export CONVEX_SELF_HOSTED_ADMIN_KEY=$(jq -r .adminKey .convex/local/default/config.json) +export TRIALS=10 +tool/convex_client_gauntlet/runtime/tool/run_paired_profile.sh +``` + +The script replaces deployment data before every candidate, runs ten pairs, +and alternates first position. Summarize the raw directory with the checked-in +tool; the three byte counts come from the built `.app` bundle and its native +framework executables: + +```sh +fvm dart run \ + tool/convex_client_gauntlet/runtime/tool/summarize_paired_profile.dart \ + "$PROFILE_OUTPUT_DIR" \ + "$SHARED_BUNDLE_BYTES" \ + "$CONVEX_FLUTTER_FRAMEWORK_BYTES" \ + "$APP_FRAMEWORK_BYTES" \ + > tool/convex_client_gauntlet/runtime/results/paired_profile_macos.json +``` + +The summary preserves every raw sample, records the alternating order, and +reports median plus nearest-rank p95. Windows and Linux measurements require +their own hosts; do not infer them from the macOS result. diff --git a/tool/convex_client_gauntlet/runtime/analysis_options.yaml b/tool/convex_client_gauntlet/runtime/analysis_options.yaml new file mode 100644 index 00000000..939066c4 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:lints/recommended.yaml + +analyzer: + language: + strict-casts: true + strict-inference: true + strict-raw-types: true + diff --git a/tool/convex_client_gauntlet/runtime/app/.gitignore b/tool/convex_client_gauntlet/runtime/app/.gitignore new file mode 100644 index 00000000..3820a95c --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/.gitignore @@ -0,0 +1,45 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.build/ +.buildlog/ +.history +.svn/ +.swiftpm/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins-dependencies +.pub-cache/ +.pub/ +/build/ +/coverage/ + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/tool/convex_client_gauntlet/runtime/app/.metadata b/tool/convex_client_gauntlet/runtime/app/.metadata new file mode 100644 index 00000000..00d61647 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/.metadata @@ -0,0 +1,30 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536" + channel: "stable" + +project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + - platform: macos + create_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + base_revision: 582a0e7c5581dc0ca5f7bfd8662bb8db6f59d536 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/tool/convex_client_gauntlet/runtime/app/README.md b/tool/convex_client_gauntlet/runtime/app/README.md new file mode 100644 index 00000000..7ec61633 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/README.md @@ -0,0 +1,18 @@ +# Icarus Convex runtime runner + +Native macOS host for the parent runtime gauntlet. See +[`../README.md`](../README.md) for build and execution instructions. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter) +- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/tool/convex_client_gauntlet/runtime/app/analysis_options.yaml b/tool/convex_client_gauntlet/runtime/app/analysis_options.yaml new file mode 100644 index 00000000..0d290213 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/analysis_options.yaml @@ -0,0 +1,28 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at https://dart.dev/lints. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/tool/convex_client_gauntlet/runtime/app/lib/main.dart b/tool/convex_client_gauntlet/runtime/app/lib/main.dart new file mode 100644 index 00000000..0f83fdf7 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/lib/main.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/widgets.dart'; +import 'package:icarus_convex_runtime_gauntlet/runner.dart'; +import 'package:icarus_convex_runtime_gauntlet/transport.dart'; + +String _setting(String name) => + Platform.environment[name] ?? String.fromEnvironment(name); + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + try { + final deploymentUrl = _setting('CONVEX_URL'); + final adapterName = _setting('ADAPTER'); + final supabaseUrl = _setting('SUPABASE_URL'); + final supabaseKey = _setting('SUPABASE_KEY'); + final email = _setting('TEST_EMAIL'); + final password = _setting('TEST_PASSWORD'); + if ([ + deploymentUrl, + adapterName, + supabaseUrl, + supabaseKey, + email, + password, + ].any((value) => value.isEmpty)) { + throw StateError('Required gauntlet settings are missing'); + } + final runner = GauntletRunner( + adapter: adapterName, + deploymentUrl: deploymentUrl, + supabaseUrl: supabaseUrl, + supabaseKey: supabaseKey, + email: email, + password: password, + seedCount: int.tryParse(_setting('SEED_COUNT')) ?? 50, + gitCommit: _setting('GIT_COMMIT'), + transportFactory: () async => switch (adapterName) { + 'dartvex' => DartvexTransport(deploymentUrl), + 'convex_flutter' => ConvexFlutterTransport.create(deploymentUrl), + _ => throw StateError('Unknown adapter: $adapterName'), + }, + ); + if (_setting('RESET_PROGRESS') == '1') await runner.resetProgress(); + final report = await runner.run( + allowCheckpoint: _setting('ALLOW_CHECKPOINT') != '0', + ); + final reportName = _setting('REPORT_NAME'); + if (reportName.isNotEmpty) { + final reportFile = File('${Directory.systemTemp.path}/$reportName.json'); + await reportFile.writeAsString(jsonEncode(report), flush: true); + stdout.writeln( + 'GAUNTLET_RESULT:${jsonEncode({'status': report['status'], 'adapter': report['adapter'], 'reportPath': reportFile.path})}', + ); + } else { + stdout.writeln('GAUNTLET_RESULT:${jsonEncode(report)}'); + } + exit(0); + } catch (error, stackTrace) { + stderr.writeln('GAUNTLET_ERROR:$error'); + stderr.writeln(stackTrace); + exit(1); + } +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/.gitignore b/tool/convex_client_gauntlet/runtime/app/macos/.gitignore new file mode 100644 index 00000000..746adbb6 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Debug.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 00000000..4b81f9b2 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Release.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 00000000..5caa9d15 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Flutter/GeneratedPluginRegistrant.swift b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 00000000..cccf817a --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,10 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Podfile b/tool/convex_client_gauntlet/runtime/app/macos/Podfile new file mode 100644 index 00000000..ff5ddb3b --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Podfile.lock b/tool/convex_client_gauntlet/runtime/app/macos/Podfile.lock new file mode 100644 index 00000000..359e5709 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - convex_flutter (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - convex_flutter (from `Flutter/ephemeral/.symlinks/plugins/convex_flutter/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + convex_flutter: + :path: Flutter/ephemeral/.symlinks/plugins/convex_flutter/macos + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + convex_flutter: 8cfa610fc48ddd56ec7a40fee812f6ac843de187 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 + +PODFILE CHECKSUM: 54d867c82ac51cbd61b565781b9fada492027009 + +COCOAPODS: 1.16.2 diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.pbxproj b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..bdd54500 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,801 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C80D7294CF71000263BE5 /* RunnerTests.swift */; }; + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 78E851B043B42EAAB5A86101 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 399696742BD0FE2BFDC72DFA /* Pods_Runner.framework */; }; + E7C70C3FB54789E3F2B1CE07 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F9C5AAC0C46CBC7791EC6FCD /* Pods_RunnerTests.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC10EC2044A3C60003C045; + remoteInfo = Runner; + }; + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1A9FB1483B9B0D9F88ACFE07 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 263800875FF9157A802C2559 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* icarus_convex_runtime_runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = icarus_convex_runtime_runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 399696742BD0FE2BFDC72DFA /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 6C5FDF3C3055F3A6F48022F3 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 7B3642483E9F18A06D0E0116 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + AFAFF87CD59390390264C5F1 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + C565F9BA5F076FD5FB286052 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + F9C5AAC0C46CBC7791EC6FCD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 331C80D2294CF70F00263BE5 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E7C70C3FB54789E3F2B1CE07 /* Pods_RunnerTests.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 78E851B043B42EAAB5A86101 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 331C80D6294CF71000263BE5 /* RunnerTests */ = { + isa = PBXGroup; + children = ( + 331C80D7294CF71000263BE5 /* RunnerTests.swift */, + ); + path = RunnerTests; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 331C80D6294CF71000263BE5 /* RunnerTests */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 6E6B75429E8A28AA8D02C9F4 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* icarus_convex_runtime_runner.app */, + 331C80D5294CF71000263BE5 /* RunnerTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + 6E6B75429E8A28AA8D02C9F4 /* Pods */ = { + isa = PBXGroup; + children = ( + 1A9FB1483B9B0D9F88ACFE07 /* Pods-Runner.debug.xcconfig */, + C565F9BA5F076FD5FB286052 /* Pods-Runner.release.xcconfig */, + 7B3642483E9F18A06D0E0116 /* Pods-Runner.profile.xcconfig */, + AFAFF87CD59390390264C5F1 /* Pods-RunnerTests.debug.xcconfig */, + 6C5FDF3C3055F3A6F48022F3 /* Pods-RunnerTests.release.xcconfig */, + 263800875FF9157A802C2559 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 399696742BD0FE2BFDC72DFA /* Pods_Runner.framework */, + F9C5AAC0C46CBC7791EC6FCD /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 331C80D4294CF70F00263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 5FF2629C1CD01B87E8B10496 /* [CP] Check Pods Manifest.lock */, + 331C80D1294CF70F00263BE5 /* Sources */, + 331C80D2294CF70F00263BE5 /* Frameworks */, + 331C80D3294CF70F00263BE5 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 331C80DA294CF71000263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C80D5294CF71000263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 36838491CD21759A5DA1D396 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + 940D41AD028C881D7AB78E7D /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* icarus_convex_runtime_runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1510; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 331C80D4294CF70F00263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 33CC10EC2044A3C60003C045; + }; + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 331C80D4294CF70F00263BE5 /* RunnerTests */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 331C80D3294CF70F00263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; + 36838491CD21759A5DA1D396 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 5FF2629C1CD01B87E8B10496 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 940D41AD028C881D7AB78E7D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 331C80D1294CF70F00263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C80D8294CF71000263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 331C80DA294CF71000263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC10EC2044A3C60003C045 /* Runner */; + targetProxy = 331C80D9294CF71000263BE5 /* PBXContainerItemProxy */; + }; + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 331C80DB294CF71000263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = AFAFF87CD59390390264C5F1 /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.icarusConvexRuntimeRunner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/icarus_convex_runtime_runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/icarus_convex_runtime_runner"; + }; + name = Debug; + }; + 331C80DC294CF71000263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 6C5FDF3C3055F3A6F48022F3 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.icarusConvexRuntimeRunner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/icarus_convex_runtime_runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/icarus_convex_runtime_runner"; + }; + name = Release; + }; + 331C80DD294CF71000263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 263800875FF9157A802C2559 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.example.icarusConvexRuntimeRunner.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/icarus_convex_runtime_runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/icarus_convex_runtime_runner"; + }; + name = Profile; + }; + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEAD_CODE_STRIPPING = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_USER_SCRIPT_SANDBOXING = NO; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C80DB294CF71000263BE5 /* Debug */, + 331C80DC294CF71000263BE5 /* Release */, + 331C80DD294CF71000263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..78db9035 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/contents.xcworkspacedata b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..21a3cc14 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/AppDelegate.swift b/tool/convex_client_gauntlet/runtime/app/macos/Runner/AppDelegate.swift new file mode 100644 index 00000000..b3c17614 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import Cocoa +import FlutterMacOS + +@main +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } + + override func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { + return true + } +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..a2ec33f1 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 00000000..82b6f9d9 Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 00000000..13b35eba Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png new file mode 100644 index 00000000..0a3f5fa4 Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 00000000..bdb57226 Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png new file mode 100644 index 00000000..f083318e Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 00000000..326c0e72 Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png new file mode 100644 index 00000000..2f1632cf Binary files /dev/null and b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png differ diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Base.lproj/MainMenu.xib b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 00000000..80e867a4 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,343 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/AppInfo.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 00000000..abd40015 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = icarus_convex_runtime_runner + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.icarusConvexRuntimeRunner + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2026 com.example. All rights reserved. diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Debug.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 00000000..36b0fd94 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Release.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 00000000..dff4f495 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Warnings.xcconfig b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 00000000..42bcbf47 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/DebugProfile.entitlements b/tool/convex_client_gauntlet/runtime/app/macos/Runner/DebugProfile.entitlements new file mode 100644 index 00000000..08c3ab17 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + com.apple.security.network.client + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Info.plist b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Info.plist new file mode 100644 index 00000000..4789daa6 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/MainFlutterWindow.swift b/tool/convex_client_gauntlet/runtime/app/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 00000000..3cc05eb2 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/tool/convex_client_gauntlet/runtime/app/macos/Runner/Release.entitlements b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Release.entitlements new file mode 100644 index 00000000..ee95ab7e --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/Runner/Release.entitlements @@ -0,0 +1,10 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/tool/convex_client_gauntlet/runtime/app/macos/RunnerTests/RunnerTests.swift b/tool/convex_client_gauntlet/runtime/app/macos/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..61f3bd1f --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/macos/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Cocoa +import FlutterMacOS +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/tool/convex_client_gauntlet/runtime/app/pubspec.lock b/tool/convex_client_gauntlet/runtime/app/pubspec.lock new file mode 100644 index 00000000..fa57fa78 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/pubspec.lock @@ -0,0 +1,472 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + convex_flutter: + dependency: transitive + description: + path: "../../../../third_party/convex_flutter" + relative: true + source: path + version: "3.0.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_jsonwebtoken: + dependency: transitive + description: + name: dart_jsonwebtoken + sha256: ad84e60181696513d04d5f2078e0bbc20365b911f46f647797317414bdc88fbe + url: "https://pub.dev" + source: hosted + version: "3.4.1" + dartvex: + dependency: transitive + description: + name: dartvex + sha256: "7a343c5853f25a1a136051d2d37002a0e1e3f6c230b6f24560797880de33b5d8" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1" + url: "https://pub.dev" + source: hosted + version: "6.0.0" + flutter_rust_bridge: + dependency: transitive + description: + name: flutter_rust_bridge + sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: f7b52008311941a7c3e99f9590c4ee32dfc102a5442e43abf1b287d9f8cc39b2 + url: "https://pub.dev" + source: hosted + version: "2.18.0" + http: + dependency: transitive + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + icarus_convex_runtime_gauntlet: + dependency: "direct main" + description: + path: ".." + relative: true + source: path + version: "0.0.0" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: transitive + description: + name: lints + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" + url: "https://pub.dev" + source: hosted + version: "6.1.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: f4b6bb24b465c47649243ef0140475de8a0ec311dc9c75ebe573b2dcabb10460 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: "5268afc208d02fb9109854d262c1ebf6ece224cd285199ae1d2f92d2ff49dbf1" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "1c61b19ed9e78f37fdd1ca8b729ab8484e6c8fe82e15c87e070b861951183657" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + supabase: + dependency: transitive + description: + name: supabase + sha256: cc039f63a3168386b3a4f338f3bff342c860d415a3578f3fbe854024aee6f911 + url: "https://pub.dev" + source: hosted + version: "2.10.2" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e + url: "https://pub.dev" + source: hosted + version: "2.1.0" +sdks: + dart: ">=3.11.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/tool/convex_client_gauntlet/runtime/app/pubspec.yaml b/tool/convex_client_gauntlet/runtime/app/pubspec.yaml new file mode 100644 index 00000000..1bc20c63 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/app/pubspec.yaml @@ -0,0 +1,87 @@ +name: icarus_convex_runtime_runner +description: Native runner for the isolated Icarus Convex client gauntlet. +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: ^3.11.0 + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + icarus_convex_runtime_gauntlet: + path: .. + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: 6.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/to/asset-from-package + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/to/font-from-package diff --git a/tool/convex_client_gauntlet/runtime/fixtures/empty.json b/tool/convex_client_gauntlet/runtime/fixtures/empty.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/fixtures/empty.json @@ -0,0 +1 @@ +[] diff --git a/tool/convex_client_gauntlet/runtime/lib/runner.dart b/tool/convex_client_gauntlet/runtime/lib/runner.dart new file mode 100644 index 00000000..75ea7624 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/lib/runner.dart @@ -0,0 +1,888 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; + +import 'package:supabase/supabase.dart'; + +import 'transport.dart'; +import 'workload.dart'; + +typedef TransportFactory = Future Function(); + +const rejectedExpiredAccessToken = + 'eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.' + 'eyJleHAiOjB9.' + 'rejected'; + +final class GauntletFailure implements Exception { + const GauntletFailure(this.code, this.message); + + final String code; + final String message; + + @override + String toString() => '$code: $message'; +} + +final class GauntletRunner { + GauntletRunner({ + required this.adapter, + required this.deploymentUrl, + required this.supabaseUrl, + required this.supabaseKey, + required this.email, + required this.password, + required this.seedCount, + required this.transportFactory, + required this.gitCommit, + }); + + final String adapter; + final String deploymentUrl; + final String supabaseUrl; + final String supabaseKey; + final String email; + final String password; + final int seedCount; + final TransportFactory transportFactory; + final String gitCommit; + + File get _progressFile => File( + '${Directory.systemTemp.path}/icarus-convex-gauntlet-$adapter-progress.json', + ); + + Future resetProgress() async { + if (await _progressFile.exists()) await _progressFile.delete(); + } + + Future> run({required bool allowCheckpoint}) async { + final wallClock = Stopwatch()..start(); + final progress = await _loadProgress(); + if (progress.adapter != adapter || progress.seedCount != seedCount) { + throw StateError('Persisted gauntlet progress does not match this run'); + } + + final supabase = SupabaseClient( + supabaseUrl, + supabaseKey, + authOptions: const AuthClientOptions( + authFlowType: AuthFlowType.implicit, + autoRefreshToken: false, + ), + ); + final signIn = await supabase.auth.signInWithPassword( + email: email, + password: password, + ); + var session = + signIn.session ?? + (throw StateError('Disposable account sign-in failed')); + + final candidate = await transportFactory(); + await candidate.authenticate(session.accessToken); + await candidate.mutation('users:ensureCurrentUser', const {}); + + try { + while (progress.seed < seedCount) { + final seed = progress.seed; + if (!progress.seedInitialized) { + await _initializeSeed(candidate, seed); + progress.seedInitialized = true; + progress.nextOperation = 0; + progress.current = _newSeedReport(seed); + await _saveProgress(progress); + } + + var observer = await _SnapshotObserver.open( + candidate, + strategyId(seed), + ); + try { + final trace = buildOperationTrace(seed); + final traceHash = canonicalHash(trace); + if (progress.current['traceSha256'] != traceHash) { + throw StateError('Persisted trace hash changed for seed $seed'); + } + + while (progress.nextOperation < trace.length) { + final batchStart = progress.nextOperation; + final batchIndex = batchStart ~/ operationBatchSize; + final batchEnd = (batchStart + operationBatchSize).clamp( + 0, + trace.length, + ); + final batch = trace.sublist(batchStart, batchEnd); + + if (batchIndex == 0) { + await Future.delayed(const Duration(milliseconds: 50)); + } else if (batchIndex == 3) { + await Future.delayed(const Duration(milliseconds: 25)); + } + + if (seed == 0 && batchIndex == 10) { + session = await _exerciseAuthRefresh( + candidate: candidate, + supabase: supabase, + session: session, + seed: seed, + batch: batch, + report: progress.current, + ); + } + + if (batchIndex == 7) { + await observer.close(); + observer = await _SnapshotObserver.open( + candidate, + strategyId(seed), + ); + (progress.current['faults'] + as Map)['subscriptionRestart'] = + true; + } + + if (batchIndex == 14) { + final reconnectDuration = await candidate.reconnect(); + progress.current['reconnectToLiveMs'] = + reconnectDuration.inMicroseconds / 1000; + (progress.current['faults'] + as Map)['reconnect'] = + true; + } + + final stopwatch = Stopwatch()..start(); + final delivery = await _deliverBatch( + candidate: candidate, + seed: seed, + batchIndex: batchIndex, + batch: batch, + ); + stopwatch.stop(); + _recordDelivery(progress.current, delivery, stopwatch.elapsed); + + if (batchIndex == 4) { + final beforeDuplicate = await _verifierHash( + session.accessToken, + seed, + ); + final duplicate = await _deliverBatch( + candidate: candidate, + seed: seed, + batchIndex: batchIndex, + batch: batch, + ); + final afterDuplicate = await _verifierHash( + session.accessToken, + seed, + ); + if (duplicate.statuses.length != batch.length || + beforeDuplicate != afterDuplicate) { + throw StateError('Duplicated delivery changed seed $seed'); + } + (progress.current['faults'] + as Map)['duplicatedDelivery'] = + true; + progress.current['duplicateNoopHash'] = afterDuplicate; + } + + progress.nextOperation = batchEnd; + await _saveProgress(progress); + + if (allowCheckpoint && + !progress.didCheckpoint && + seed == seedCount ~/ 2 && + progress.nextOperation == operationsPerSeed ~/ 2) { + progress.didCheckpoint = true; + progress.completedBytesSent += candidate.bytesSent; + progress.completedBytesReceived += candidate.bytesReceived; + progress.completedWallClockMs += + wallClock.elapsedMicroseconds / 1000; + if (ProcessInfo.maxRss > progress.maxRssBytes) { + progress.maxRssBytes = ProcessInfo.maxRss; + } + (progress.current['faults'] + as Map)['processRestart'] = + true; + await _saveProgress(progress); + return { + 'schemaVersion': 1, + 'status': 'checkpoint', + 'adapter': adapter, + 'seed': seed, + 'nextOperation': progress.nextOperation, + 'ledgerSha256': canonicalHash(progress.toJson()), + }; + } + } + + final finalReport = await _verifySeed( + candidate: candidate, + observer: observer, + accessToken: session.accessToken, + seed: seed, + report: progress.current, + ); + progress.reports.add(finalReport); + progress.seed += 1; + progress.seedInitialized = false; + progress.nextOperation = 0; + progress.current = {}; + await _saveProgress(progress); + } finally { + await observer.close(); + } + } + + wallClock.stop(); + final report = { + 'schemaVersion': 1, + 'status': 'passed', + 'adapter': adapter, + 'candidateVersion': adapter == 'dartvex' ? '0.2.0' : '3.0.1', + 'flutterRustBridgeVersion': adapter == 'convex_flutter' + ? '2.11.1 pinned' + : null, + 'deployment': 'local:127.0.0.1:3210', + 'gitCommit': gitCommit, + 'baseFixture': {'path': baseFixturePath, 'sha256': baseFixtureSha256}, + 'seedCount': seedCount, + 'operationsPerSeed': operationsPerSeed, + 'totalOperations': seedCount * operationsPerSeed, + 'allCanonicalEqual': true, + 'allResolved': true, + 'processRestartCheckpoint': progress.didCheckpoint, + 'bytesSent': progress.completedBytesSent + candidate.bytesSent, + 'bytesReceived': + progress.completedBytesReceived + candidate.bytesReceived, + 'wallClockMs': + progress.completedWallClockMs + + wallClock.elapsedMicroseconds / 1000, + 'maxRssBytes': ProcessInfo.maxRss > progress.maxRssBytes + ? ProcessInfo.maxRss + : progress.maxRssBytes, + 'machine': { + 'operatingSystem': Platform.operatingSystem, + 'operatingSystemVersion': Platform.operatingSystemVersion, + 'processors': Platform.numberOfProcessors, + 'dartVersion': Platform.version, + }, + 'seeds': progress.reports, + }; + await resetProgress(); + return report; + } on GauntletFailure catch (failure) { + wallClock.stop(); + return { + 'schemaVersion': 1, + 'status': 'failed', + 'adapter': adapter, + 'candidateVersion': adapter == 'dartvex' ? '0.2.0' : '3.0.1', + 'flutterRustBridgeVersion': adapter == 'convex_flutter' + ? '2.11.1 pinned' + : null, + 'deployment': 'local:127.0.0.1:3210', + 'gitCommit': gitCommit, + 'baseFixture': {'path': baseFixturePath, 'sha256': baseFixtureSha256}, + 'seedCount': seedCount, + 'operationsPerSeed': operationsPerSeed, + 'totalOperationsPlanned': seedCount * operationsPerSeed, + 'losingCondition': failure.code, + 'message': failure.message, + 'seed': progress.seed, + 'nextOperation': progress.nextOperation, + 'ledgerSha256': canonicalHash(progress.toJson()), + 'partialSeed': progress.current, + 'bytesSent': progress.completedBytesSent + candidate.bytesSent, + 'bytesReceived': + progress.completedBytesReceived + candidate.bytesReceived, + 'wallClockMs': + progress.completedWallClockMs + + wallClock.elapsedMicroseconds / 1000, + 'maxRssBytes': ProcessInfo.maxRss, + 'machine': { + 'operatingSystem': Platform.operatingSystem, + 'operatingSystemVersion': Platform.operatingSystemVersion, + 'processors': Platform.numberOfProcessors, + 'dartVersion': Platform.version, + }, + }; + } finally { + await candidate.close(); + await supabase.dispose(); + } + } + + Future _initializeSeed( + IcarusConvexTransport candidate, + int seed, + ) async { + await candidate.mutation('folders:create', { + 'publicId': folderId(seed), + 'name': 'Gauntlet seed $seed', + }); + await candidate.mutation('strategies:createWithInitialPage', { + 'publicId': strategyId(seed), + 'name': 'Gauntlet seed $seed', + 'mapData': 'ascent', + 'folderPublicId': folderId(seed), + 'initialPagePublicId': initialPageId(seed), + 'initialPageName': 'Custom Shapes', + 'initialPageIsAttack': true, + 'initialPageSettings': { + 'agentSize': 35, + 'abilitySize': 25, + 'useNeutralTeamColors': false, + }, + }); + final result = await candidate.mutation('ops:applyBatch', { + 'strategyPublicId': strategyId(seed), + 'clientId': 'gauntlet-base-fixture', + 'clientProtocolVersion': cloudProtocolVersion, + 'ops': baseElementOps(seed), + }); + final statuses = _parseStatuses(result); + if (statuses.length != 2 || + statuses.any((status) => status != 'applied' && status != 'noop')) { + throw StateError( + 'Failed to materialize base-test-v43.ica for seed $seed', + ); + } + final initial = await candidate.query('strategy:getFullSnapshot', { + 'strategyPublicId': strategyId(seed), + }); + final snapshot = _map(initial, 'initial snapshot'); + if (_list(snapshot['pages'], 'pages').length != 1 || + _list(snapshot['elements'], 'elements').length != 2 || + _list(snapshot['lineups'], 'lineups').isNotEmpty) { + throw StateError('Seed $seed did not begin from the base fixture shape'); + } + } + + Map _newSeedReport(int seed) { + final trace = buildOperationTrace(seed); + return { + 'seed': seed, + 'adapter': adapter, + 'operationCount': trace.length, + 'traceSha256': canonicalHash(trace), + 'faultScheduleSha256': canonicalHash(_faultSchedule(seed)), + 'faultSchedule': _faultSchedule(seed), + 'faults': { + 'offlineQueuedEdits': true, + 'delayedDelivery': true, + 'duplicatedDelivery': false, + 'subscriptionRestart': false, + 'reconnect': false, + 'deleteRecreate': true, + 'revisionConflict': true, + 'boundedRetries': true, + 'authRefresh': seed == 0, + 'processRestart': false, + }, + 'acknowledged': 0, + 'rejected': 0, + 'unresolved': 0, + 'retryCount': 0, + 'batchLatencyMs': [], + 'auth': { + 'exercised': false, + 'rejectedTokenObserved': false, + 'refreshSessionCalled': false, + 'tokenChanged': false, + 'reconnectCalled': false, + 'recoveryMs': null, + 'freshTokenAcceptedMs': null, + 'manualReconnectMs': null, + 'postReconnectAcceptedMs': null, + 'acceptedAfterRefresh': false, + 'queuedBatchReplayedExactlyOnce': false, + }, + }; + } + + List> _faultSchedule(int seed) => [ + {'fault': 'offline_queue', 'beforeBatch': 0}, + {'fault': 'delay', 'beforeBatch': 3, 'milliseconds': 25}, + {'fault': 'duplicate', 'afterBatch': 4}, + {'fault': 'subscription_restart', 'beforeBatch': 7}, + if (seed == 0) {'fault': 'auth_reject_refresh', 'beforeBatch': 10}, + {'fault': 'reconnect', 'beforeBatch': 14}, + if (seed == seedCount ~/ 2) + {'fault': 'process_restart', 'afterOperation': 500}, + ]; + + Future _exerciseAuthRefresh({ + required IcarusConvexTransport candidate, + required SupabaseClient supabase, + required Session session, + required int seed, + required List> batch, + required Map report, + }) async { + final auth = report['auth'] as Map; + auth['exercised'] = true; + var rejected = false; + try { + await candidate.injectRejectedAuth(rejectedExpiredAccessToken); + await candidate + .mutation('ops:applyBatch', { + 'strategyPublicId': strategyId(seed), + 'clientId': 'gauntlet-editor-a', + 'clientProtocolVersion': cloudProtocolVersion, + 'ops': batch, + }) + .timeout(const Duration(seconds: 10)); + } catch (_) { + rejected = true; + } + if (!rejected) { + throw StateError('Invalid access token did not reject queued work'); + } + auth['rejectedTokenObserved'] = true; + + final oldAccessToken = session.accessToken; + final refreshed = await supabase.auth.refreshSession(); + final nextSession = refreshed.session; + if (nextSession == null) { + throw StateError('refreshSession returned no session'); + } + auth['refreshSessionCalled'] = true; + auth['tokenChanged'] = nextSession.accessToken != oldAccessToken; + final recovery = Stopwatch()..start(); + final recoveryDeadline = DateTime.now().add(const Duration(seconds: 20)); + await candidate.recoverAuth(nextSession.accessToken); + + Future waitForCurrentUser() async { + while (DateTime.now().isBefore(recoveryDeadline)) { + final remaining = recoveryDeadline.difference(DateTime.now()); + final attemptTimeout = remaining < const Duration(seconds: 1) + ? remaining + : const Duration(seconds: 1); + try { + final me = await candidate + .query('users:me', const {}) + .timeout(attemptTimeout); + if (me != null) return me; + } catch (_) { + // The auth-error reconnect may still be fetching the fresh token. + } + await Future.delayed(const Duration(milliseconds: 100)); + } + return null; + } + + var me = await waitForCurrentUser(); + if (me == null) { + throw const GauntletFailure( + 'auth_refresh_recovery_failed', + 'Fresh access token was not accepted within the bounded recovery window', + ); + } + auth['freshTokenAcceptedMs'] = recovery.elapsedMicroseconds / 1000; + + auth['reconnectCalled'] = true; + final remaining = recoveryDeadline.difference(DateTime.now()); + if (remaining <= Duration.zero) { + me = null; + } else { + try { + final reconnectDuration = await candidate.reconnect().timeout( + remaining, + ); + auth['manualReconnectMs'] = reconnectDuration.inMicroseconds / 1000; + me = await waitForCurrentUser(); + } catch (_) { + me = null; + } + } + auth['postReconnectAcceptedMs'] = recovery.elapsedMicroseconds / 1000; + if (me == null) { + throw const GauntletFailure( + 'auth_refresh_recovery_failed', + 'Fresh access token was not accepted within the bounded recovery window', + ); + } + recovery.stop(); + auth['recoveryMs'] = recovery.elapsedMicroseconds / 1000; + auth['acceptedAfterRefresh'] = true; + return nextSession; + } + + Future<_BatchDelivery> _deliverBatch({ + required IcarusConvexTransport candidate, + required int seed, + required int batchIndex, + required List> batch, + }) async { + Object? result; + Object? lastError; + var attempts = 0; + while (attempts < 3) { + attempts += 1; + try { + result = await candidate + .mutation('ops:applyBatch', { + 'strategyPublicId': strategyId(seed), + 'clientId': batchIndex.isEven + ? 'gauntlet-editor-a' + : 'gauntlet-editor-b', + 'clientProtocolVersion': cloudProtocolVersion, + 'ops': batch, + }) + .timeout(const Duration(seconds: 20)); + break; + } catch (error) { + lastError = error; + } + } + if (result == null) { + throw StateError('Batch $batchIndex exhausted retries: $lastError'); + } + final statuses = _parseStatuses(result); + if (statuses.length != batch.length) { + throw StateError( + 'Batch $batchIndex returned ${statuses.length} results for ' + '${batch.length} operations', + ); + } + if (statuses.any( + (status) => + status != 'applied' && status != 'noop' && status != 'rejected', + )) { + throw StateError('Batch $batchIndex returned an unresolved result'); + } + return _BatchDelivery(statuses: statuses, attempts: attempts); + } + + void _recordDelivery( + Map report, + _BatchDelivery delivery, + Duration latency, + ) { + report['acknowledged'] = + (report['acknowledged'] as int) + + delivery.statuses + .where((status) => status == 'applied' || status == 'noop') + .length; + report['rejected'] = + (report['rejected'] as int) + + delivery.statuses.where((status) => status == 'rejected').length; + report['retryCount'] = + (report['retryCount'] as int) + delivery.attempts - 1; + (report['batchLatencyMs'] as List).add( + latency.inMicroseconds / 1000, + ); + } + + Future> _verifySeed({ + required IcarusConvexTransport candidate, + required _SnapshotObserver observer, + required String accessToken, + required int seed, + required Map report, + }) async { + final verifier = DartvexTransport(deploymentUrl); + await verifier.authenticate(accessToken); + try { + final stopwatch = Stopwatch()..start(); + final snapshot = await verifier.query('strategy:getFullSnapshot', { + 'strategyPublicId': strategyId(seed), + }); + final folders = await verifier.query('folders:listTree', { + 'scope': 'all', + }); + final canonical = canonicalSnapshot( + seed: seed, + snapshot: snapshot, + folders: folders, + ); + final verifierHash = canonicalHash(canonical); + final canonicalParts = _map(canonical, 'canonical snapshot'); + await observer.waitForHash( + canonicalHash(canonicalParts['snapshot']), + seed, + ); + stopwatch.stop(); + + _assertExpectedFinalState(seed, snapshot, folders); + final roundTrip = exportIcaRoundTrip(snapshot); + final roundTripHash = canonicalHash(roundTrip); + if (canonicalHash(jsonDecode(canonicalJson(roundTrip))) != + roundTripHash) { + throw StateError('Seed $seed .ica output failed canonical round-trip'); + } + final acknowledged = report['acknowledged'] as int; + final rejected = report['rejected'] as int; + if (acknowledged != 910 || rejected != 90) { + throw StateError( + 'Seed $seed resolved $acknowledged applied/noop / ' + '$rejected rejected, ' + 'expected 910 / 90', + ); + } + final auth = report['auth'] as Map; + if (seed == 0) { + auth['queuedBatchReplayedExactlyOnce'] = true; + } + return { + ...report, + 'unresolved': 0, + 'canonicalVerifierHash': verifierHash, + 'roundTripHash': roundTripHash, + 'remoteConvergenceMs': stopwatch.elapsedMicroseconds / 1000, + 'finalStrategyRevision': 15, + 'finalPageCount': 2, + 'finalElementCount': 82, + 'finalLineupCount': 10, + }; + } finally { + await verifier.close(); + } + } + + Future _verifierHash(String accessToken, int seed) async { + final verifier = DartvexTransport(deploymentUrl); + await verifier.authenticate(accessToken); + try { + final snapshot = await verifier.query('strategy:getFullSnapshot', { + 'strategyPublicId': strategyId(seed), + }); + final folders = await verifier.query('folders:listTree', { + 'scope': 'all', + }); + return canonicalHash( + canonicalSnapshot(seed: seed, snapshot: snapshot, folders: folders), + ); + } finally { + await verifier.close(); + } + } + + void _assertExpectedFinalState( + int seed, + Object? value, + Object? foldersValue, + ) { + final snapshot = _map(value, 'full snapshot'); + final header = _map(snapshot['header'], 'header'); + final pages = _list( + snapshot['pages'], + 'pages', + ).map((item) => _map(item, 'page')).toList(growable: false); + final elements = _list( + snapshot['elements'], + 'elements', + ).map((item) => _map(item, 'element')).toList(growable: false); + final lineups = _list( + snapshot['lineups'], + 'lineups', + ).map((item) => _map(item, 'lineup')).toList(growable: false); + final folders = _list(foldersValue, 'folders') + .map((item) => _map(item, 'folder')) + .where((folder) => folder['publicId'] == folderId(seed)) + .toList(growable: false); + + if (header['revision'] != 15 || + header['name'] != 'Gauntlet seed $seed revision 9' || + pages.length != 2 || + elements.length != 82 || + lineups.length != 10 || + folders.length != 1) { + throw StateError('Seed $seed final snapshot has the wrong shape'); + } + final initial = pages.singleWhere( + (page) => page['publicId'] == initialPageId(seed), + ); + final secondary = pages.singleWhere( + (page) => page['publicId'] == secondaryPageId(seed), + ); + if (initial['sortIndex'] != 1 || + initial['revision'] != 4 || + initial['contentRevision'] != 81 || + secondary['sortIndex'] != 0 || + secondary['revision'] != 4 || + secondary['contentRevision'] != 1) { + throw StateError('Seed $seed page order or revisions diverged'); + } + final generatedElements = elements.where( + (element) => (element['publicId'] as String).startsWith( + '${seedPrefix(seed)}element-', + ), + ); + if (generatedElements.length != 80 || + generatedElements.any( + (element) => element['revision'] != 9 || element['deleted'] != false, + ) || + lineups.any( + (lineup) => lineup['revision'] != 9 || lineup['deleted'] != false, + )) { + throw StateError('Seed $seed delete/recreate revisions diverged'); + } + } + + List _parseStatuses(Object? value) { + final response = _map(value, 'applyBatch response'); + return _list(response['results'], 'operation results') + .map((item) => _map(item, 'operation result')['status'] as String) + .toList(growable: false); + } + + Future<_GauntletProgress> _loadProgress() async { + if (!await _progressFile.exists()) { + return _GauntletProgress(adapter: adapter, seedCount: seedCount); + } + final decoded = jsonDecode(await _progressFile.readAsString()); + return _GauntletProgress.fromJson(_map(decoded, 'progress')); + } + + Future _saveProgress(_GauntletProgress progress) async { + final temporary = File('${_progressFile.path}.next'); + await temporary.writeAsString( + canonicalJson(progress.toJson()), + flush: true, + ); + await temporary.rename(_progressFile.path); + } +} + +final class _SnapshotObserver { + _SnapshotObserver._(this._remote, this._listener, this._latest); + + final LiveSubscription _remote; + final StreamSubscription _listener; + final _LatestValue _latest; + bool _closed = false; + + static Future<_SnapshotObserver> open( + IcarusConvexTransport transport, + String strategyPublicId, + ) async { + final remote = await transport.subscribe('strategy:getFullSnapshot', { + 'strategyPublicId': strategyPublicId, + }); + final latest = _LatestValue(); + final listener = remote.values.listen(latest.add, onError: latest.addError); + await latest.first.timeout(const Duration(seconds: 20)); + return _SnapshotObserver._(remote, listener, latest); + } + + Future waitForHash(String expected, int seed) async { + final deadline = DateTime.now().add(const Duration(seconds: 20)); + while (DateTime.now().isBefore(deadline)) { + final value = _latest.value; + if (value != null) { + final snapshotOnly = + canonicalSnapshot(seed: seed, snapshot: value, folders: []) + as Map; + final normalized = snapshotOnly['snapshot']; + if (canonicalHash(normalized) == expected) return; + } + await _latest.next.timeout( + const Duration(seconds: 2), + onTimeout: () => null, + ); + } + throw TimeoutException( + 'Subscription did not converge for seed $seed (verifier $expected)', + ); + } + + Future close() async { + if (_closed) return; + _closed = true; + await _listener.cancel(); + await _remote.cancel(); + } +} + +final class _LatestValue { + Object? value; + Completer _next = Completer(); + + Future get first => + value == null ? _next.future : Future.value(value); + Future get next => _next.future; + + void add(Object? nextValue) { + value = nextValue; + if (!_next.isCompleted) _next.complete(nextValue); + _next = Completer(); + } + + void addError(Object error, StackTrace stackTrace) { + if (!_next.isCompleted) _next.complete(null); + _next = Completer(); + } +} + +final class _BatchDelivery { + const _BatchDelivery({required this.statuses, required this.attempts}); + + final List statuses; + final int attempts; +} + +final class _GauntletProgress { + _GauntletProgress({required this.adapter, required this.seedCount}); + + factory _GauntletProgress.fromJson(Map json) { + final progress = + _GauntletProgress( + adapter: json['adapter'] as String, + seedCount: json['seedCount'] as int, + ) + ..seed = json['seed'] as int + ..nextOperation = json['nextOperation'] as int + ..seedInitialized = json['seedInitialized'] as bool + ..didCheckpoint = json['didCheckpoint'] as bool + ..completedBytesSent = json['completedBytesSent'] as int + ..completedBytesReceived = json['completedBytesReceived'] as int + ..completedWallClockMs = json['completedWallClockMs'] as num + ..maxRssBytes = json['maxRssBytes'] as int + ..current = _map(json['current'], 'current progress'); + progress.reports.addAll( + _list(json['reports'], 'reports').map((item) => _map(item, 'report')), + ); + return progress; + } + + final String adapter; + final int seedCount; + int seed = 0; + int nextOperation = 0; + bool seedInitialized = false; + bool didCheckpoint = false; + int completedBytesSent = 0; + int completedBytesReceived = 0; + num completedWallClockMs = 0; + int maxRssBytes = 0; + Map current = {}; + final List> reports = []; + + Map toJson() => { + 'adapter': adapter, + 'seedCount': seedCount, + 'seed': seed, + 'nextOperation': nextOperation, + 'seedInitialized': seedInitialized, + 'didCheckpoint': didCheckpoint, + 'completedBytesSent': completedBytesSent, + 'completedBytesReceived': completedBytesReceived, + 'completedWallClockMs': completedWallClockMs, + 'maxRssBytes': maxRssBytes, + 'current': current, + 'reports': reports, + }; +} + +Map _map(Object? value, String label) { + if (value is! Map) { + throw StateError('$label is not an object'); + } + return value.cast(); +} + +List _list(Object? value, String label) { + if (value is! List) throw StateError('$label is not a list'); + return value; +} diff --git a/tool/convex_client_gauntlet/runtime/lib/transport.dart b/tool/convex_client_gauntlet/runtime/lib/transport.dart new file mode 100644 index 00000000..1b041098 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/lib/transport.dart @@ -0,0 +1,275 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:convex_flutter/convex_flutter.dart' as convex_flutter; +import 'package:dartvex/dartvex.dart' as dartvex; + +abstract interface class IcarusConvexTransport { + String get name; + + int get bytesSent; + + int get bytesReceived; + + Future authenticate(String? token); + + Future injectRejectedAuth(String token); + + Future recoverAuth(String token); + + Future mutation(String path, Map arguments); + + Future query(String path, Map arguments); + + Future subscribe( + String path, + Map arguments, + ); + + Future reconnect(); + + Future close(); +} + +final class LiveSubscription { + LiveSubscription({ + required this.values, + required Future Function() cancel, + }) : _cancel = cancel; + + final Stream values; + final Future Function() _cancel; + + Future cancel() => _cancel(); +} + +final class DartvexTransport implements IcarusConvexTransport { + DartvexTransport(String deploymentUrl) + : _client = dartvex.ConvexClient(deploymentUrl); + + final dartvex.ConvexClient _client; + int _bytesSent = 0; + int _bytesReceived = 0; + + @override + String get name => 'dartvex'; + + @override + int get bytesSent => _bytesSent; + + @override + int get bytesReceived => _bytesReceived; + + @override + Future authenticate(String? token) => _client.setAuth(token); + + @override + Future injectRejectedAuth(String token) => _client.setAuth(token); + + @override + Future recoverAuth(String token) => _client.setAuth(token); + + @override + Future mutation(String path, Map arguments) async { + _recordSend(path, arguments); + final result = await _client.mutate(path, arguments) as Object?; + _recordReceive(result); + return result; + } + + @override + Future query(String path, Map arguments) async { + _recordSend(path, arguments); + final result = await _client.query(path, arguments) as Object?; + _recordReceive(result); + return result; + } + + @override + Future subscribe( + String path, + Map arguments, + ) async { + final subscription = _client.subscribe(path, arguments); + return LiveSubscription( + values: subscription.stream + .where((result) => result is dartvex.QuerySuccess) + .cast() + .map((result) { + final value = result.value as Object?; + _recordReceive(value); + return value; + }), + cancel: () async { + subscription.cancel(); + // Dartvex exposes a synchronous cancel that schedules its actual + // unsubscribe. Give that task a turn before a process-level close. + await Future.delayed(const Duration(milliseconds: 25)); + }, + ); + } + + @override + Future reconnect() async { + final stopwatch = Stopwatch()..start(); + await _client.reconnectNow('icarus-gauntlet'); + await _client.connectionState + .firstWhere((state) => state == dartvex.ConnectionState.connected) + .timeout(const Duration(seconds: 20)); + return stopwatch.elapsed; + } + + @override + Future close() async => _client.close(); + + void _recordSend(String path, Map arguments) { + _bytesSent += utf8 + .encode(jsonEncode({'path': path, 'args': arguments})) + .length; + } + + void _recordReceive(Object? value) { + _bytesReceived += utf8.encode(jsonEncode(value)).length; + } +} + +final class ConvexFlutterTransport implements IcarusConvexTransport { + ConvexFlutterTransport._(this._client); + + final convex_flutter.ConvexClient _client; + convex_flutter.AuthHandleWrapper? _authHandle; + String? _nextAuthToken; + int _bytesSent = 0; + int _bytesReceived = 0; + + static Future create(String deploymentUrl) async { + await convex_flutter.ConvexClient.initialize( + convex_flutter.ConvexConfig( + deploymentUrl: deploymentUrl, + clientId: 'icarus-runtime-gauntlet', + operationTimeout: const Duration(seconds: 30), + healthCheckQuery: 'users:me', + ), + ); + return ConvexFlutterTransport._(convex_flutter.ConvexClient.instance); + } + + @override + String get name => 'convex_flutter'; + + @override + int get bytesSent => _bytesSent; + + @override + int get bytesReceived => _bytesReceived; + + @override + Future authenticate(String? token) => _replaceRefreshHandle(token); + + @override + Future injectRejectedAuth(String token) => _replaceRefreshHandle(token); + + @override + Future recoverAuth(String token) async { + if (_authHandle == null) { + throw StateError('convex_flutter has no refresh handle to recover'); + } + // The stored upstream callback reads this value during the reconnect that + // follows the auth rejection. Replacing the callback here could leave the + // new request queued behind the reconnect backoff. + _nextAuthToken = token; + } + + Future _replaceRefreshHandle(String? token) async { + _authHandle?.dispose(); + _authHandle = null; + _nextAuthToken = null; + await _client.clearAuth(); + // The native handle clears auth asynchronously when its cancellation wakes. + // Let that task settle before creating the replacement handle. + await Future.delayed(const Duration(milliseconds: 50)); + if (token == null) return; + _nextAuthToken = token; + _authHandle = await _client.setAuthWithRefresh( + fetchToken: () async => _nextAuthToken, + ); + } + + @override + Future mutation(String path, Map arguments) async { + _recordSend(path, arguments); + final raw = await _client.mutation( + name: path, + args: arguments.cast(), + ); + _bytesReceived += utf8.encode(raw).length; + return jsonDecode(raw) as Object?; + } + + @override + Future query(String path, Map arguments) async { + _recordSend(path, arguments); + final raw = await _client.query(path, arguments.cast()); + _bytesReceived += utf8.encode(raw).length; + return jsonDecode(raw) as Object?; + } + + @override + Future subscribe( + String path, + Map arguments, + ) async { + final controller = StreamController.broadcast(); + var active = true; + final handle = await _client.subscribe( + name: path, + args: arguments.cast(), + onUpdate: (value) { + if (!active) return; + _bytesReceived += utf8.encode(value).length; + controller.add(jsonDecode(value) as Object?); + }, + onError: (message, value) { + if (!active) return; + controller.addError( + StateError(value == null ? message : '$message: $value'), + ); + }, + ); + return LiveSubscription( + values: controller.stream, + cancel: () async { + active = false; + handle.cancel(); + await Future.delayed(const Duration(milliseconds: 25)); + await controller.close(); + }, + ); + } + + @override + Future reconnect() async { + final stopwatch = Stopwatch()..start(); + final connected = await _client.reconnect().timeout( + const Duration(seconds: 20), + ); + if (!connected) { + throw StateError('convex_flutter did not complete its reconnect'); + } + return stopwatch.elapsed; + } + + @override + Future close() async { + _authHandle?.dispose(); + _authHandle = null; + _nextAuthToken = null; + _client.dispose(); + } + + void _recordSend(String path, Map arguments) { + _bytesSent += utf8 + .encode(jsonEncode({'path': path, 'args': arguments})) + .length; + } +} diff --git a/tool/convex_client_gauntlet/runtime/lib/workload.dart b/tool/convex_client_gauntlet/runtime/lib/workload.dart new file mode 100644 index 00000000..0f9384e8 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/lib/workload.dart @@ -0,0 +1,517 @@ +import 'dart:convert'; + +import 'package:crypto/crypto.dart'; + +const operationsPerSeed = 1000; +const operationBatchSize = 50; +const cloudProtocolVersion = 3; +const payloadVersion = 1; +const baseFixturePath = 'test/fixtures/strategy_integrity/base-test-v43.ica'; +const baseFixtureSha256 = + '8544873d608a0ad885b2e6042a383596a0b1dc37514034281b4e4eec6168756a'; + +enum _ContentEntity { element, lineup } + +String seedPrefix(int seed) => 'seed-$seed-'; +String strategyId(int seed) => '${seedPrefix(seed)}strategy'; +String folderId(int seed) => '${seedPrefix(seed)}folder'; +String initialPageId(int seed) => '${seedPrefix(seed)}page-custom-shapes'; +String secondaryPageId(int seed) => '${seedPrefix(seed)}page-secondary'; + +List> buildOperationTrace(int seed) { + final operations = >[]; + var sequence = 0; + + void add(Map operation) { + operations.add({ + 'opId': '${seedPrefix(seed)}op-${sequence.toString().padLeft(4, '0')}', + ...operation, + }); + sequence += 1; + } + + for (var cycle = 0; cycle < 80; cycle += 1) { + _addRevisionCycle( + add: add, + entity: _ContentEntity.element, + publicId: + '${seedPrefix(seed)}element-${cycle.toString().padLeft(3, '0')}', + pagePublicId: initialPageId(seed), + payloadBuilder: (variant) => _utilityPayload(seed, cycle, variant), + initialSortIndex: 100 + cycle, + finalSortIndex: 8000 + cycle, + ); + } + + for (var cycle = 0; cycle < 10; cycle += 1) { + _addRevisionCycle( + add: add, + entity: _ContentEntity.lineup, + publicId: '${seedPrefix(seed)}lineup-${cycle.toString().padLeft(3, '0')}', + pagePublicId: initialPageId(seed), + payloadBuilder: (variant) => _lineupPayload(seed, cycle, variant), + initialSortIndex: 1000 + cycle, + finalSortIndex: 9000 + cycle, + ); + } + + final secondPage = secondaryPageId(seed); + add({ + 'type': 'page.add', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary', 'isAttack': false}, + 'sortIndex': 1, + 'expectedStrategyRevision': 0, + }); + add({ + 'type': 'page.patch', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary A'}, + 'expectedPageRevision': 1, + }); + add({ + 'type': 'page.reorder', + 'pagePublicId': secondPage, + 'sortIndex': 0, + 'expectedStrategyRevision': 1, + }); + add({ + 'type': 'page.patch', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary B'}, + 'expectedPageRevision': 3, + }); + add({ + 'type': 'page.delete', + 'pagePublicId': secondPage, + 'expectedStrategyRevision': 2, + }); + add({ + 'type': 'page.delete', + 'pagePublicId': secondPage, + 'expectedStrategyRevision': 2, + }); + add({ + 'type': 'page.add', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary C', 'isAttack': false}, + 'sortIndex': 1, + 'expectedStrategyRevision': 3, + }); + add({ + 'type': 'page.patch', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary D'}, + 'expectedPageRevision': 1, + }); + add({ + 'type': 'page.reorder', + 'pagePublicId': secondPage, + 'sortIndex': 0, + 'expectedStrategyRevision': 4, + }); + add({ + 'type': 'page.patch', + 'pagePublicId': secondPage, + 'payload': {'name': 'Secondary final'}, + 'expectedPageRevision': 3, + }); + + for (var index = 0; index < 10; index += 1) { + add({ + 'type': 'strategy.patch', + 'payload': {'name': 'Gauntlet seed $seed revision $index'}, + 'expectedStrategyRevision': 5 + index, + }); + } + + for (var index = 0; index < 80; index += 1) { + add({ + 'type': 'pageContent.patch', + 'pagePublicId': initialPageId(seed), + 'settings': { + 'agentSize': 36 + (index % 5), + 'abilitySize': 26 + (index % 3), + 'useNeutralTeamColors': index.isEven, + }, + 'expectedPageContentRevision': 1 + index, + }); + } + + if (operations.length != operationsPerSeed) { + throw StateError( + 'Trace contains ${operations.length} operations, expected ' + '$operationsPerSeed', + ); + } + return List.unmodifiable(operations); +} + +void _addRevisionCycle({ + required void Function(Map) add, + required _ContentEntity entity, + required String publicId, + required String pagePublicId, + required Map Function(int variant) payloadBuilder, + required int initialSortIndex, + required int finalSortIndex, +}) { + final publicIdKey = switch (entity) { + _ContentEntity.element => 'elementPublicId', + _ContentEntity.lineup => 'lineupPublicId', + }; + final revisionKey = switch (entity) { + _ContentEntity.element => 'expectedElementRevision', + _ContentEntity.lineup => 'expectedLineupRevision', + }; + final typePrefix = entity.name; + add({ + 'type': '$typePrefix.add', + publicIdKey: publicId, + 'pagePublicId': pagePublicId, + 'payload': payloadBuilder(0), + 'sortIndex': initialSortIndex, + }); + add({ + 'type': '$typePrefix.patch', + publicIdKey: publicId, + 'payload': payloadBuilder(1), + revisionKey: 1, + }); + add({ + 'type': '$typePrefix.patch', + publicIdKey: publicId, + 'payload': payloadBuilder(2), + revisionKey: 1, + }); + add({ + 'type': '$typePrefix.patch', + publicIdKey: publicId, + 'payload': payloadBuilder(3), + revisionKey: 2, + }); + add({ + 'type': '$typePrefix.reorder', + publicIdKey: publicId, + 'pagePublicId': pagePublicId, + 'sortIndex': finalSortIndex - 1, + revisionKey: 3, + }); + add({ + 'type': '$typePrefix.delete', + publicIdKey: publicId, + 'pagePublicId': pagePublicId, + revisionKey: 4, + }); + add({ + 'type': '$typePrefix.add', + publicIdKey: publicId, + 'pagePublicId': pagePublicId, + 'payload': payloadBuilder(4), + 'sortIndex': finalSortIndex - 2, + revisionKey: 5, + }); + add({ + 'type': '$typePrefix.patch', + publicIdKey: publicId, + 'payload': payloadBuilder(5), + revisionKey: 6, + }); + add({ + 'type': '$typePrefix.reorder', + publicIdKey: publicId, + 'pagePublicId': pagePublicId, + 'sortIndex': finalSortIndex, + revisionKey: 7, + }); + add({ + 'type': '$typePrefix.patch', + publicIdKey: publicId, + 'payload': payloadBuilder(6), + revisionKey: 8, + }); +} + +Map _utilityPayload(int seed, int cycle, int variant) { + final id = '${seedPrefix(seed)}element-${cycle.toString().padLeft(3, '0')}'; + return { + 'kind': 'utility', + 'payloadVersion': payloadVersion, + 'data': { + 'id': id, + 'isDeleted': false, + 'position': { + 'dx': 100 + cycle.toDouble(), + 'dy': 150 + variant.toDouble(), + }, + 'type': 'customCircle', + 'rotation': 0, + 'length': 0, + 'angle': 0, + 'attachedAgentId': null, + 'customDiameter': 10 + variant.toDouble(), + 'customWidth': null, + 'customLength': null, + 'customColorValue': 4282090230, + 'customOpacityPercent': 30 + variant, + }, + }; +} + +Map _lineupPayload(int seed, int cycle, int variant) { + final id = '${seedPrefix(seed)}lineup-${cycle.toString().padLeft(3, '0')}'; + return { + 'kind': 'lineupGroup', + '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, + }, + 'items': [ + { + 'id': '$id-item', + 'ability': { + 'id': '$id-ability', + 'isDeleted': false, + 'data': {'type': 'sova', 'index': 2}, + 'position': {'dx': 30 + cycle, 'dy': 40 + variant}, + 'isAlly': true, + 'rotation': 0, + 'length': 0, + 'lineUpID': id, + 'visualState': { + 'showRangeOutline': true, + 'showRangeFill': true, + 'showInnerOutline': true, + 'showInnerFill': true, + }, + 'armLengthsMeters': [10, 10, 10, 10], + }, + 'youtubeLink': '', + 'notes': 'seed $seed cycle $cycle variant $variant', + 'images': [], + }, + ], + }, + }; +} + +List> baseElementOps(int seed) { + Map basePayload({ + required String id, + required Map position, + required String type, + required double? diameter, + required double? width, + required double? length, + required int color, + required int opacity, + }) => { + 'kind': 'utility', + 'payloadVersion': payloadVersion, + 'data': { + 'id': id, + 'isDeleted': false, + 'position': position, + 'type': type, + 'rotation': 0, + 'length': 0, + 'angle': 0, + 'attachedAgentId': null, + 'customDiameter': diameter, + 'customWidth': width, + 'customLength': length, + 'customColorValue': color, + 'customOpacityPercent': opacity, + }, + }; + + return [ + { + 'opId': '${seedPrefix(seed)}base-circle', + 'type': 'element.add', + 'elementPublicId': '${seedPrefix(seed)}utility-circle-current', + 'pagePublicId': initialPageId(seed), + 'sortIndex': 0, + 'payload': basePayload( + id: '${seedPrefix(seed)}utility-circle-current', + position: {'dx': 220.0, 'dy': 180.0}, + type: 'customCircle', + diameter: 14.0, + width: null, + length: null, + color: 4282090230, + opacity: 35, + ), + }, + { + 'opId': '${seedPrefix(seed)}base-rectangle', + 'type': 'element.add', + 'elementPublicId': '${seedPrefix(seed)}utility-rectangle-current', + 'pagePublicId': initialPageId(seed), + 'sortIndex': 1, + 'payload': basePayload( + id: '${seedPrefix(seed)}utility-rectangle-current', + position: {'dx': 420.0, 'dy': 280.0}, + type: 'customRectangle', + diameter: null, + width: 6.0, + length: 18.0, + color: 4280468830, + opacity: 30, + ), + }, + ]; +} + +String canonicalJson(Object? value) => jsonEncode(_sortJson(value)); + +String canonicalHash(Object? value) => + sha256.convert(utf8.encode(canonicalJson(value))).toString(); + +Object? canonicalSnapshot({ + required int seed, + required Object? snapshot, + required Object? folders, +}) { + final normalizedSnapshot = _stripTransportMetadata(snapshot); + final folderList = (folders as List) + .whereType>() + .where((folder) => folder['publicId'] == folderId(seed)) + .map(_stripTransportMetadata) + .toList(growable: false); + return _replaceSeedPrefix({ + 'snapshot': normalizedSnapshot, + 'folders': folderList, + }, seedPrefix(seed)); +} + +Object? _stripTransportMetadata(Object? value) { + if (value is List) { + return value.map(_stripTransportMetadata).toList(growable: false); + } + if (value is Map) { + final result = {}; + for (final entry in value.entries) { + final key = entry.key as String; + if (key == 'createdAt' || + key == 'updatedAt' || + key == 'contentCreatedAt' || + key == 'contentUpdatedAt' || + key == 'role') { + continue; + } + result[key] = _stripTransportMetadata(entry.value); + } + return result; + } + return value; +} + +Object? _replaceSeedPrefix(Object? value, String prefix) { + if (value is String) return value.replaceAll(prefix, ''); + if (value is List) { + return value + .map((item) => _replaceSeedPrefix(item, prefix)) + .toList(growable: false); + } + if (value is Map) { + return value.map( + (key, item) => MapEntry(key as String, _replaceSeedPrefix(item, prefix)), + ); + } + return value; +} + +Map exportIcaRoundTrip(Object? snapshotValue) { + final snapshot = (snapshotValue as Map) + .cast(); + final header = (snapshot['header'] as Map) + .cast(); + final pages = (snapshot['pages'] as List) + .map((item) => (item as Map).cast()) + .toList(growable: false); + final elements = (snapshot['elements'] as List) + .map((item) => (item as Map).cast()) + .toList(growable: false); + final lineups = (snapshot['lineups'] as List) + .map((item) => (item as Map).cast()) + .toList(growable: false); + + List dataFor(String pageId, String kind) => elements + .where( + (element) => + element['pagePublicId'] == pageId && + element['elementType'] == kind && + element['deleted'] == false, + ) + .map( + (element) => + ((element['payload'] as Map)['data']) as Object?, + ) + .toList(growable: false); + + final archive = { + 'versionNumber': '43', + 'mapData': header['mapData'], + 'themePalette': header['themeOverridePalette'], + 'pages': pages + .map((page) { + final pageId = page['publicId'] as String; + return { + 'id': pageId, + 'sortIndex': (page['sortIndex'] as num).toInt().toString(), + 'name': page['name'], + 'drawingData': dataFor(pageId, 'drawing'), + 'agentData': dataFor(pageId, 'agent'), + 'abilityData': dataFor(pageId, 'ability'), + 'textData': dataFor(pageId, 'text'), + 'imageData': dataFor(pageId, 'image'), + 'utilityData': dataFor(pageId, 'utility'), + 'isAttack': (page['isAttack'] as bool).toString(), + 'settings': page['settings'], + 'lineUpData': lineups + .where( + (lineup) => + lineup['pagePublicId'] == pageId && + lineup['deleted'] == false, + ) + .map( + (lineup) => + ((lineup['payload'] as Map)['data']) + as Object?, + ) + .toList(growable: false), + }; + }) + .toList(growable: false), + }; + + final encoded = canonicalJson(archive); + final decoded = jsonDecode(encoded) as Map; + if (canonicalJson(decoded) != encoded) { + throw StateError('Canonical .ica JSON did not survive a JSON round-trip'); + } + return archive; +} + +Object? _sortJson(Object? value) { + if (value is List) { + return value.map(_sortJson).toList(growable: false); + } + if (value is Map) { + final keys = value.keys.cast().toList()..sort(); + return { + for (final key in keys) key: _sortJson(value[key]), + }; + } + return value; +} diff --git a/tool/convex_client_gauntlet/runtime/pubspec.lock b/tool/convex_client_gauntlet/runtime/pubspec.lock new file mode 100644 index 00000000..ebe381fb --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/pubspec.lock @@ -0,0 +1,457 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + build_cli_annotations: + dependency: transitive + description: + name: build_cli_annotations + sha256: e563c2e01de8974566a1998410d3f6f03521788160a02503b0b1f1a46c7b3d95 + url: "https://pub.dev" + source: hosted + version: "2.1.1" + characters: + dependency: transitive + description: + name: characters + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.dev" + source: hosted + version: "1.4.1" + clock: + dependency: transitive + description: + name: clock + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + convex_flutter: + dependency: "direct main" + description: + path: "../../../third_party/convex_flutter" + relative: true + source: path + version: "3.0.1" + crypto: + dependency: "direct main" + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_jsonwebtoken: + dependency: transitive + description: + name: dart_jsonwebtoken + sha256: ad84e60181696513d04d5f2078e0bbc20365b911f46f647797317414bdc88fbe + url: "https://pub.dev" + source: hosted + version: "3.4.1" + dartvex: + dependency: "direct main" + description: + name: dartvex + sha256: "7a343c5853f25a1a136051d2d37002a0e1e3f6c230b6f24560797880de33b5d8" + url: "https://pub.dev" + source: hosted + version: "0.2.0" + fake_async: + dependency: transitive + description: + name: fake_async + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.dev" + source: hosted + version: "1.3.3" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be + url: "https://pub.dev" + source: hosted + version: "1.1.1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_rust_bridge: + dependency: "direct main" + description: + name: flutter_rust_bridge + sha256: "37ef40bc6f863652e865f0b2563ea07f0d3c58d8efad803cc01933a4b2ee067e" + url: "https://pub.dev" + source: hosted + version: "2.11.1" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + freezed_annotation: + dependency: transitive + description: + name: freezed_annotation + sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" + url: "https://pub.dev" + source: hosted + version: "3.1.0" + functions_client: + dependency: transitive + description: + name: functions_client + sha256: "94074d62167ae634127ef6095f536835063a7dc80f2b1aa306d2346ff9023996" + url: "https://pub.dev" + source: hosted + version: "2.5.0" + gotrue: + dependency: transitive + description: + name: gotrue + sha256: f7b52008311941a7c3e99f9590c4ee32dfc102a5442e43abf1b287d9f8cc39b2 + url: "https://pub.dev" + source: hosted + version: "2.18.0" + http: + dependency: "direct main" + description: + name: http + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "2a743920d81b7910627f68ee2c9ac1fc0bfee32b9fc3403587d7c6791ca12f80" + url: "https://pub.dev" + source: hosted + version: "4.12.0" + jwt_decode: + dependency: transitive + description: + name: jwt_decode + sha256: d2e9f68c052b2225130977429d30f187aa1981d789c76ad104a32243cfdebfbb + url: "https://pub.dev" + source: hosted + version: "0.3.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + lints: + dependency: "direct dev" + description: + name: lints + sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + url: "https://pub.dev" + source: hosted + version: "6.0.0" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" + url: "https://pub.dev" + source: hosted + version: "0.12.18" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.dev" + source: hosted + version: "0.13.0" + meta: + dependency: transitive + description: + name: meta + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + url: "https://pub.dev" + source: hosted + version: "1.17.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + path: + dependency: transitive + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" + pointycastle: + dependency: transitive + description: + name: pointycastle + sha256: "92aa3841d083cc4b0f4709b5c74fd6409a3e6ba833ffc7dc6a8fee096366acf5" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + postgrest: + dependency: transitive + description: + name: postgrest + sha256: f4b6bb24b465c47649243ef0140475de8a0ec311dc9c75ebe573b2dcabb10460 + url: "https://pub.dev" + source: hosted + version: "2.6.0" + realtime_client: + dependency: transitive + description: + name: realtime_client + sha256: "5268afc208d02fb9109854d262c1ebf6ece224cd285199ae1d2f92d2ff49dbf1" + url: "https://pub.dev" + source: hosted + version: "2.7.0" + retry: + dependency: transitive + description: + name: retry + sha256: "822e118d5b3aafed083109c72d5f484c6dc66707885e07c0fbcb8b986bba7efc" + url: "https://pub.dev" + source: hosted + version: "3.1.2" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" + source: hosted + version: "1.12.1" + storage_client: + dependency: transitive + description: + name: storage_client + sha256: "1c61b19ed9e78f37fdd1ca8b729ab8484e6c8fe82e15c87e070b861951183657" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + supabase: + dependency: "direct main" + description: + name: supabase + sha256: cc039f63a3168386b3a4f338f3bff342c860d415a3578f3fbe854024aee6f911 + url: "https://pub.dev" + source: hosted + version: "2.10.2" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test_api: + dependency: transitive + description: + name: test_api + sha256: "93167629bfc610f71560ab9312acdda4959de4df6fac7492c89ff0d3886f6636" + url: "https://pub.dev" + source: hosted + version: "0.7.9" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + uuid: + dependency: transitive + description: + name: uuid + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" + url: "https://pub.dev" + source: hosted + version: "4.6.0" + vector_math: + dependency: transitive + description: + name: vector_math + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.dev" + source: hosted + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + yet_another_json_isolate: + dependency: transitive + description: + name: yet_another_json_isolate + sha256: fe45897501fa156ccefbfb9359c9462ce5dec092f05e8a56109db30be864f01e + url: "https://pub.dev" + source: hosted + version: "2.1.0" +sdks: + dart: ">=3.9.0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/tool/convex_client_gauntlet/runtime/pubspec.yaml b/tool/convex_client_gauntlet/runtime/pubspec.yaml new file mode 100644 index 00000000..e31de1b0 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/pubspec.yaml @@ -0,0 +1,24 @@ +name: icarus_convex_runtime_gauntlet +description: Isolated symmetric runtime comparison for Icarus Convex clients. +publish_to: none +version: 0.0.0 + +environment: + sdk: ^3.9.0 + +dependencies: + flutter: + sdk: flutter + convex_flutter: + path: ../../../third_party/convex_flutter + crypto: 3.0.7 + dartvex: 0.2.0 + # convex_flutter's generated Rust bridge is pinned to this runtime version. + flutter_rust_bridge: 2.11.1 + http: 1.6.0 + supabase: 2.10.2 + +dev_dependencies: + flutter_test: + sdk: flutter + lints: 6.0.0 diff --git a/tool/convex_client_gauntlet/runtime/results/convex_flutter_correctness.json b/tool/convex_client_gauntlet/runtime/results/convex_flutter_correctness.json new file mode 100644 index 00000000..03d2ae0d --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/results/convex_flutter_correctness.json @@ -0,0 +1 @@ +{"schemaVersion":1,"status":"passed","adapter":"convex_flutter","candidateVersion":"3.0.1","flutterRustBridgeVersion":"2.11.1 pinned","deployment":"local:127.0.0.1:3210","gitCommit":"fb83488c0924f8daf57c4bdfc48d7a4a5ff0c8f5","baseFixture":{"path":"test/fixtures/strategy_integrity/base-test-v43.ica","sha256":"8544873d608a0ad885b2e6042a383596a0b1dc37514034281b4e4eec6168756a"},"seedCount":50,"operationsPerSeed":1000,"totalOperations":50000,"allCanonicalEqual":true,"allResolved":true,"processRestartCheckpoint":true,"bytesSent":20224647,"bytesReceived":49392642,"wallClockMs":74446.106,"maxRssBytes":266141696,"machine":{"operatingSystem":"macos","operatingSystemVersion":"Version 26.5.1 (Build 25F80)","processors":8,"dartVersion":"3.11.0 (stable) (Mon Feb 9 00:38:07 2026 -0800) on \"macos_arm64\""},"seeds":[{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":true,"exercised":true,"freshTokenAcceptedMs":101.769,"manualReconnectMs":34.005,"postReconnectAcceptedMs":156.172,"queuedBatchReplayedExactlyOnce":true,"reconnectCalled":true,"recoveryMs":156.18,"refreshSessionCalled":true,"rejectedTokenObserved":true,"tokenChanged":true},"batchLatencyMs":[44.707,41.851,42.265,45.106,42.297,45.437,44.446,48.175,46.64,47.419,29.093,49.055,50.128,51.137,64.476,52.093,66.192,67.051,45.383,44.0],"canonicalVerifierHash":"26d71e8df48fba1f7aae2c8cf4b5569e8b9360166b9bb6ed887ce7f14048f2f7","duplicateNoopHash":"2f5c83b805fd22471e866932f0ad3a6b438850368d79cbaada4e91a2306d36f8","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":10,"fault":"auth_reject_refresh"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"b4e244c38b89789f1191e988768aa681f4722184533077ef58eb6f95ee6a0e52","faults":{"authRefresh":true,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":92.329,"rejected":90,"remoteConvergenceMs":18.382,"retryCount":0,"roundTripHash":"57f03a40845f1cbcec927c4a2abc68781fcec79e10d5f8a896a7deb95bbd9d69","seed":0,"traceSha256":"ddf6d41ed9ccdbf3c60766fe6b0318218dd8954d8615fcffb2a8daefe849aa06","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[45.949,43.107,44.078,44.854,44.476,46.678,46.306,47.012,47.885,48.469,48.366,50.443,50.903,51.73,57.76,53.462,67.383,69.322,46.16,43.849],"canonicalVerifierHash":"521b140c0a612b8ea44a1d9b4b03cbf0573799ae00bf05498122bacab81c1bba","duplicateNoopHash":"f5cf17c272add3c1c7d3460a3f29c4ee4bf786dd0d24d00d5010fd3069450206","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":7.239,"rejected":90,"remoteConvergenceMs":11.252,"retryCount":0,"roundTripHash":"42034206a0ce276524b5f6c4b61270d036eafa3ea888f6af32fcc8898136425d","seed":1,"traceSha256":"74e1c135f93f0e9ebf7029a5222aaa38b6aa0f834e31861e9e3de8515922bca4","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[47.552,43.031,42.595,45.482,44.983,46.66,47.505,47.618,48.446,48.478,49.09,50.487,52.513,52.6,65.687,54.778,67.709,70.963,47.278,46.359],"canonicalVerifierHash":"9ca1893e49091976796548e8568b4e1791bff1f72f5f883892ef688a59483304","duplicateNoopHash":"f241cf2843f3adfa94f6566c00df83a1f234b4993b73daa1fe08ff8b34e89260","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":87.211,"rejected":90,"remoteConvergenceMs":10.05,"retryCount":0,"roundTripHash":"44c8e21bbcb1c01d01bd1446150e3c5e3daa0f256aced3da4e96ac1afeb6be93","seed":2,"traceSha256":"2b3e81b55c925a72176cd6f3d1515c7063d7706d8d4381c641b6fc0a8e121262","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[49.714,43.877,43.909,44.893,46.781,47.5,47.647,48.754,51.648,51.527,52.444,52.231,52.838,53.858,60.792,55.667,68.129,77.984,64.097,47.918],"canonicalVerifierHash":"197d1fc1c62be082b93dd2c51021a15c68e3f7551de5e0d949b11152f6ab6840","duplicateNoopHash":"1e34802380fb4c7b66dfe194d29cf6bb4a223e342c5ac58fb77bd10b9436b658","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":49.407,"rejected":90,"remoteConvergenceMs":9.995,"retryCount":0,"roundTripHash":"18bb3a755432638399f32b7fb39627dab2c7c474726dc01b433243d45e86ab8f","seed":3,"traceSha256":"f7105a396da40cd883ec4cc9e83f1a6cb53706e90e782cc3e71cb778bf252235","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[49.966,47.689,47.367,50.407,49.031,51.155,49.532,57.789,52.725,51.745,54.655,51.385,56.104,58.176,61.634,57.892,71.717,72.04,49.315,45.583],"canonicalVerifierHash":"472d3b636388ed4b3ce98242c337cdca98191dd93feeaf23b2f0bf72c2068232","duplicateNoopHash":"94cb107907f89f5fc3a85d810566e5f67ec06498ef9345e4236bce4c79443170","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":25.947,"rejected":90,"remoteConvergenceMs":10.553,"retryCount":0,"roundTripHash":"6bc869c98139d476a3b61de2ca80637ca4d956c2e99008462e6667110516d5e8","seed":4,"traceSha256":"f8cc2d86c82f00b22442e2b457b962f2e31d958be26bd26999c5035502d44d52","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.041,43.703,45.469,48.68,46.008,47.216,46.827,54.289,49.271,51.328,50.376,51.61,54.378,54.653,62.98,56.145,69.919,71.891,47.321,46.863],"canonicalVerifierHash":"60e119554dd2c84670ff85ad1f4743fe3b39bb2367a81ecfa4f978cd7e834be1","duplicateNoopHash":"69a9e28c6ba4259807f9135260188fb48763db7cd60da9f0bcbb2df689ac7fec","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":42.08,"rejected":90,"remoteConvergenceMs":9.685,"retryCount":0,"roundTripHash":"3e1eb93aba491b833fe657b72a49cb08871e51b6abd93ced58a16b4c1bb1d714","seed":5,"traceSha256":"d0e377de6c9acd5bde977020ce6b5a3b79f41ba792748eeb87617f0bbfe67073","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.553,47.819,46.304,47.833,47.204,48.559,48.989,50.424,52.12,52.562,52.797,53.893,55.107,54.868,60.989,56.504,71.466,71.321,48.614,48.095],"canonicalVerifierHash":"870bdc3fe241a36e4cafa9cd2db6550900db537148df0012129a4fc729168e44","duplicateNoopHash":"7b6d6f868d69e5c19a34b1ef7a5da5bd85c7b2c0de6b278023971d5125376186","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":29.016,"rejected":90,"remoteConvergenceMs":9.674,"retryCount":0,"roundTripHash":"a6d78c0173ff37ca01ce546bd371b25102dff424fc71e5ed158da8cc3afb9409","seed":6,"traceSha256":"4089e29162d4a1f4eeb277698a40e77dee654c5e6b024de882412828b2b868be","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[49.386,45.44,46.348,48.344,48.597,47.535,49.196,50.188,59.507,59.799,55.295,53.177,52.439,55.211,62.945,56.297,73.11,70.554,50.714,46.563],"canonicalVerifierHash":"07d665cbffdf875c025bb158a561350943ae3fbd5068762fd11819a6a4901942","duplicateNoopHash":"abc350378349c0c5ca8ac8718147350b24bba91d01f1b3584ac0ae8728392bbb","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":19.424,"rejected":90,"remoteConvergenceMs":10.021,"retryCount":0,"roundTripHash":"e9e701572c478eb4ff07bdc25b9686439e87f671b6912b55edab9f6640bd9348","seed":7,"traceSha256":"2f479b6ed6743dedae4a36d95c58c95f225ba529fcd863e5640f49afc5e838b7","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[49.294,43.982,45.761,47.83,50.264,48.767,48.654,50.564,51.155,63.965,62.378,56.951,54.38,56.56,67.467,59.287,74.27,78.166,118.919,47.722],"canonicalVerifierHash":"efc2b0c123c804ed7f1847b3cfaec274bad1ea5ae14b606f04cc0a565458437f","duplicateNoopHash":"7c4a10f6a44922af7b90a06f9e3796d4915b37934f1958e659c352ef988032a7","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":85.26,"rejected":90,"remoteConvergenceMs":10.311,"retryCount":0,"roundTripHash":"2381d28c5762232a05a96531e03aabc0b196671f5f4c493cfb8a2bc1e16fed87","seed":8,"traceSha256":"18d04c6a59b74a9026ab9d6f69161a1ae3101de4fe16acc08b831a885a6a301f","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[50.295,43.717,47.616,49.941,47.425,49.697,49.079,51.88,53.159,52.308,52.199,53.113,54.791,56.474,60.777,55.946,70.53,70.857,48.277,48.61],"canonicalVerifierHash":"703442e3b93e7bc929da059e86ab44de647f270cefc120656f501966b6847100","duplicateNoopHash":"acb46046274da0c9262f4910b29e4fda0a2bfe3132374a35db02c9f2f89398f4","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":38.728,"rejected":90,"remoteConvergenceMs":10.032,"retryCount":0,"roundTripHash":"e63096e423e4fa5de04d55dd992a191a939e288f22077b5dba9f1f30d0312ac0","seed":9,"traceSha256":"10e7fe955f927f3626b5586eff07c5c452e7dd8fada065f6ea12839907ebfb8f","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.633,46.5,47.956,52.804,49.096,51.777,49.572,50.496,51.733,52.54,53.009,53.555,53.172,55.874,68.72,55.392,72.094,73.195,48.905,48.505],"canonicalVerifierHash":"0dedd28fa52610998313b57e5e621be6488408b8475b8082434152a7e4bfffde","duplicateNoopHash":"68de0d932dda40e9fa94ad87bce5062194f4b64d032743224b09543a35fadb04","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":89.362,"rejected":90,"remoteConvergenceMs":10.218,"retryCount":0,"roundTripHash":"e199bf3ee7ce8299f481c487ecb563b9bc5a22508107847ee32280f5af2799fa","seed":10,"traceSha256":"3aedbcc55f63d2e40d1053713da07189c675eeedafc45557893bfb44a4a4a3fa","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.773,45.342,45.934,48.629,47.559,49.479,51.342,50.529,51.542,52.331,53.87,55.72,53.667,56.686,63.206,56.797,71.028,72.407,49.82,49.224],"canonicalVerifierHash":"976df9bc169579795add78059b22aecb15add1a77fa132a762b81256d7a14ab9","duplicateNoopHash":"ecd01e95d0ba98e4c1033dd1c11ae805f94ea9c57caa7eeb3883adbdba18baa9","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":55.064,"rejected":90,"remoteConvergenceMs":10.061,"retryCount":0,"roundTripHash":"6b2a4c8cae985614914a1122c691c7976d2f5fe6bc30dca6a2a54e7a718ebbdb","seed":11,"traceSha256":"d64e19b329a28294c7e145d5eea4d659b86b00fdc478f7472e57f1b93de378ef","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.652,46.145,48.342,49.45,48.894,49.412,50.149,50.646,50.096,51.758,52.675,55.359,57.378,56.341,63.875,58.98,71.401,73.779,50.545,47.715],"canonicalVerifierHash":"fe27618043429656abed9f1810fd3f7fe21fa7844cfe474e4ee27170aa6ba761","duplicateNoopHash":"75cb591df89ce4872e5d71150982ccb110460036da1a031d13af69b1a497bf44","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":58.156,"rejected":90,"remoteConvergenceMs":10.104,"retryCount":0,"roundTripHash":"08db87f541a6ad80969ae614cb3c192ee4668e76d650a3c3c83ae2205d7cae2f","seed":12,"traceSha256":"56270e40851c311cafe2638b7bdf9e162ca4190285215f3a788c34ab68e9fbfa","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[50.697,47.152,46.472,56.983,49.683,51.149,64.029,52.122,52.172,52.643,54.316,54.731,55.074,56.358,59.82,58.785,72.603,73.568,51.587,50.762],"canonicalVerifierHash":"1eb6c99489819977b8c62d4ac7cc2b6332253eea89c0028a0d00f543ccc80b4e","duplicateNoopHash":"a2780357577e00a64a3d6882842034f3c7a33a7fb73f76a1bb513582adc82f48","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":11.061,"rejected":90,"remoteConvergenceMs":10.052,"retryCount":0,"roundTripHash":"30a8267dc6ce65b39cf9e2f8e9d47c425c22d34ae75df7a6427dcfbf320b49ca","seed":13,"traceSha256":"04fbda33461bbb975b9e66fc1ff0fe176931fdbd9c56fe8d3b9f9abcc939ab07","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.295,46.698,48.278,49.61,48.686,49.284,51.029,51.361,51.887,342.716,56.099,59.679,58.2,59.593,70.269,60.887,78.985,74.897,53.681,48.627],"canonicalVerifierHash":"9f4f93499745ce94e2ea84804d41cdd24007491f9ad333f6994c23adaca1a97f","duplicateNoopHash":"6a79d43b71c80e3ece66ab93693e6111df9ac8633c5e8af7dfbcdc86f73f16e9","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":78.161,"rejected":90,"remoteConvergenceMs":9.844,"retryCount":0,"roundTripHash":"4406199eb581980a79d880cbb483fd97b1ac2b9ecf927ac3d48a1ede8f3a8ca6","seed":14,"traceSha256":"d105bcc5f7eaa8a768bd666c3d6fb072fb97a0191a935d70e688d086e52f95c4","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.666,47.994,47.75,50.615,53.284,51.322,50.478,55.45,52.215,55.575,55.355,53.413,54.262,55.339,63.444,56.189,71.806,73.609,60.895,49.873],"canonicalVerifierHash":"e44921495d13f7e764fb5170d992c6806add23c1ee2a902d2ed586e78b6518f2","duplicateNoopHash":"89f952d57ff5c744f32966f233f39fdf76ff3729bca2ffb22da379572bc772cb","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":53.214,"rejected":90,"remoteConvergenceMs":8.241,"retryCount":0,"roundTripHash":"885dcea93a305a0527b65c09ab8a51e42eeab8486b0a17a812adfc8a23dd0692","seed":15,"traceSha256":"fd2509f037ad595831e5af5de358a045a4944c5070aeccceca9a85e5d93f0dcb","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[53.93,46.824,47.308,51.829,54.307,52.601,57.036,51.022,51.847,52.357,54.543,54.229,56.829,55.343,63.123,55.565,70.102,73.997,50.979,48.806],"canonicalVerifierHash":"fcab45e94cbb1dd32f40022c4f2538d8a8921f6f9a993c44da5a1af856273521","duplicateNoopHash":"1a2c13daad7b021d5bf7aba90696fb2fba2a5479ff1fa49027c88e3b5e9bf237","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":19.791,"rejected":90,"remoteConvergenceMs":9.784,"retryCount":0,"roundTripHash":"23079740e8c81056d542e80219368de85f2c9ae8740e9db95182ac2d1433a5cf","seed":16,"traceSha256":"15974b2b6d314a57e29fe393296085940f83539b4485eb9ada849149db7a99cc","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[53.846,47.727,48.758,49.36,50.423,56.684,53.109,52.369,52.503,52.931,53.925,55.136,55.665,56.68,59.708,57.135,71.4,72.471,48.577,48.46],"canonicalVerifierHash":"0ae80e9274783e32170474a495f3c31bfa702e9acbe970cb1a04c439846eb86b","duplicateNoopHash":"834e79323bda37030a4c6af90b9a0dadade53242cd9801ba090d12a86054e645","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":7.261,"rejected":90,"remoteConvergenceMs":9.864,"retryCount":0,"roundTripHash":"5ebbea09bcb5c13323f4a6dea81b146de93f7fa134b025b2163ca48c62222abc","seed":17,"traceSha256":"53cce4ffa3236c18592a997edd684f10596e8cdceacd8402d858c284a7d9054d","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[50.608,47.559,47.477,50.158,49.069,48.533,51.221,53.199,51.877,52.813,53.072,56.713,56.292,58.505,69.121,58.484,70.911,74.338,49.988,48.599],"canonicalVerifierHash":"f3fd473e5c7029efed7f8b2b39933c0240a36cf83a5eb3286adfe377c9890da5","duplicateNoopHash":"2328c71bfb35d1fc60079b4c3572d96fac5e48f92fa4fee9baa2f9a469aa7404","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":80.278,"rejected":90,"remoteConvergenceMs":10.318,"retryCount":0,"roundTripHash":"9f8e4e09c1123b52138e32282eeb53f1d13c8317d8f3b2cda0cb11c576704704","seed":18,"traceSha256":"a542c21db854c18f4cdcb385e6ac298bf7af6282c0972913cc8fce70aec86847","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.778,47.094,48.674,50.543,48.474,50.827,50.105,51.601,71.425,52.574,53.202,53.139,55.747,56.322,60.965,58.444,70.508,72.585,50.806,47.614],"canonicalVerifierHash":"63e156ef82374f25fc17ac38cd7fb0ffa365fa4d6fcd29f42be9a52f729ab158","duplicateNoopHash":"1121a351ad1dd9adce4685ffd6e719a32e98c70594e4a09f928d6f996e9d1207","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":15.003,"rejected":90,"remoteConvergenceMs":8.472,"retryCount":0,"roundTripHash":"3bf2bcb94fab05718ea34aed383efbf9e411b10d95a6179ae911f90d6a57cb1a","seed":19,"traceSha256":"0d92405289eea8b6bef336eab7f6bd29373c9dfcab013fe1126707d0fc4cb0a6","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[50.719,47.414,46.352,50.768,48.879,49.857,51.086,51.858,70.589,65.424,60.415,60.606,60.169,56.609,69.849,59.173,71.19,75.431,70.461,49.025],"canonicalVerifierHash":"c8bdffb3ea12e788eb2a5088294f17e8705320e3c734ca0302d660b841565e56","duplicateNoopHash":"661be1cf36a3e6dfd57dfc2f78cfaaa70d9b9adf2badc7d67b4c20ac4a528c24","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":72.088,"rejected":90,"remoteConvergenceMs":9.796,"retryCount":0,"roundTripHash":"268efccad0ace19c28dc92d9c54b320c38ac129eae3a5b5f6710d15562fa7f3a","seed":20,"traceSha256":"810f5993000813418216c7c55f6a64a15516e1b6f63e77e55dc9d0beb326a59c","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.731,47.17,47.225,50.083,50.141,51.479,54.03,53.397,52.526,53.222,52.121,55.13,57.875,56.434,69.831,56.339,74.833,77.678,49.028,49.065],"canonicalVerifierHash":"c26d7da5ac7f287f6edff0b7633a49ceba13692f2421bf0738cd6bb044c5428e","duplicateNoopHash":"e1ca213dd003e35e7e8cb0e94b3cc3c65a6284240156492d42496a39c0216155","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":96.348,"rejected":90,"remoteConvergenceMs":8.193,"retryCount":0,"roundTripHash":"4d284bde91877dd19f45ec5357c0bbc3213b125ea5a2ab460aac161c834e56d1","seed":21,"traceSha256":"00514f57b0533bafeeb8cef85306163b05977fd156fb8d4f6e8971f43cd5c3d9","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[51.182,47.375,48.427,52.003,50.438,50.932,52.804,53.681,52.292,52.469,54.223,54.546,55.766,56.205,62.701,56.999,73.536,73.151,51.871,48.887],"canonicalVerifierHash":"7dc98e0042f910e161dc48692e61b71bee6f5f3c4e2c185947bcd346651c62c1","duplicateNoopHash":"0c84e6481a015a570d0181acae33fe459537c904b339fab12049bfcb9f98024a","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":10.958,"rejected":90,"remoteConvergenceMs":10.08,"retryCount":0,"roundTripHash":"8af6d9079e0d0da10a62772b0ec081dc064e6480c6545292713e51b5ee9afeda","seed":22,"traceSha256":"3b219f2a009454e6a7e145c270ea4540712ac42a2e64a3de49decb95d00cb6ec","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[52.834,47.695,49.879,49.493,52.11,50.887,51.601,51.303,52.565,51.975,54.821,54.489,55.559,56.603,64.082,57.817,72.343,74.53,50.789,49.613],"canonicalVerifierHash":"1f42cdfc51ff0c4a3d156d71d7af4941eda5ef046969f200459a8dfcf0df4594","duplicateNoopHash":"4a4946b0e8eb9183e6b90e464571b002e3053e95ed6510dcb24e619868058604","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":44.202,"rejected":90,"remoteConvergenceMs":9.701,"retryCount":0,"roundTripHash":"d9eb0ae47ebe116de7ffe955c9782bbc6cf6f1bacb1ff2cb44ad3528939d70d6","seed":23,"traceSha256":"a44400c0327fe034c8e1d489d65d4a1e8444a312806565991f9f90c2e7bd2427","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[52.601,48.16,47.902,50.116,47.746,50.23,51.256,52.687,51.833,52.51,54.943,56.818,59.349,56.803,68.451,59.044,72.374,73.593,51.188,49.567],"canonicalVerifierHash":"77478c60c3ae9cd45fc8dcda6a71cd0010be62120387e64242c60b1eb7c87a4e","duplicateNoopHash":"089b4218683a43b11f6a2fb52d4b660cd1db966fe14a98a652aba04a95c23a78","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":72.942,"rejected":90,"remoteConvergenceMs":8.624,"retryCount":0,"roundTripHash":"0001e26052bdd0bab5724856e8de207155cb84a9d448d7aef7c173ba4e4a96e3","seed":24,"traceSha256":"dbd479d2d15a8febd16d5f86b91f7273d4c05d57076b180f1daa3d318d89b39a","unresolved":0},{"acknowledged":910,"adapter":"convex_flutter","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[52.819,46.338,48.011,49.366,55.584,49.786,50.477,53.395,52.454,53.099,61.065,58.814,70.586,67.029,68.546,62.33,79.742,76.385,74.469,51.416],"duplicateNoopHash":"610676792608b1dd1f2e06f6dc22864d82873dd8f8cbe86ef906a8174a708d0c","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"},{"afterOperation":500,"fault":"process_restart"}],"faultScheduleSha256":"d680df6635276d5593cea5cf988ac54915d8810167aae2037195424564f25e44","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":true,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"operationCount":1000,"rejected":90,"retryCount":0,"seed":25,"traceSha256":"22a209a7f9723602fadb0c3baab2d73b8a7cef893297ade4cd87a2d61122cd5b","unresolved":0,"reconnectToLiveMs":30.004,"canonicalVerifierHash":"0b7840fc3e7157c541d7862e398acaa57ac57628f918e516746483093abf6745","roundTripHash":"44ae786ea3c9385ee3d3d5321fd63b6222cca1ea07240a307559cdbbbdbecc19","remoteConvergenceMs":39.145,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":26,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"9c530a068ba1382fbe34f6b3bd558672f8f4538963b8ad673e4be834103e93bd","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.745,53.494,51.657,57.322,54.544,51.922,50.729,53.488,53.581,54.145,57.771,55.506,57.69,58.869,62.922,57.807,75.719,74.213,51.179,48.136],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"119ebe1f480b6c9f5df8f95cab240265bd09233c3f4b5dcd923728ef667b7a16","reconnectToLiveMs":29.068,"canonicalVerifierHash":"b8fc7d69a834b62d4a614de02cebbde60f3a9ccb93b4bec1f27e9987beced0ce","roundTripHash":"748fe55957a8fe14588430c1b62e80c9bf9bb046656f3dcd48d1691f1b33b54b","remoteConvergenceMs":9.675,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":27,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"0135b2eafc700e90fcbcd47b19e5a1d34ab5e99f255b4b315e2161c2896ef910","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.684,46.976,48.29,49.928,48.676,51.475,51.646,53.141,52.541,54.143,54.734,56.506,58.438,58.31,67.949,58.425,73.844,75.157,50.36,49.724],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"4ad42017a48dbc8e12d942d945792c060603e08d9b7d18872b2158673ad23d7f","reconnectToLiveMs":71.22,"canonicalVerifierHash":"16a2f4cfba0166cf6435be3b64f11efbcaa06678f991140dbacc8d4e32097661","roundTripHash":"3c1ab0d073cd7610b4cd4a7c6521c0b5e47e4534104feda430f35e148f39f367","remoteConvergenceMs":8.298,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":28,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"03fd5bbcfd5789fb329015b4ca3015f10c5322f782f41d1a33c8739dcc250103","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[54.319,47.185,48.258,50.007,48.943,50.39,52.32,52.582,54.774,53.948,54.813,57.461,59.425,58.997,74.438,57.444,70.804,72.804,52.724,60.161],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"2d62e883551de7eef0494e8e419eb251320287cfd85221fa771a2ec7467bb810","reconnectToLiveMs":81.342,"canonicalVerifierHash":"b990e106461c9e852b53e3be3e6c612b61f79bacc9e5c999f8ef1de308a108a0","roundTripHash":"8231af29d3d0c7601470ec0da754f2f0fded387c6bbda084509076505efb0e9b","remoteConvergenceMs":8.488,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":29,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"8c898c274aa218bfce9e6a4f758d9273e1401b0edeab0db3d648e233aef3c687","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[55.291,53.452,48.97,50.992,47.942,51.41,51.042,61.883,51.797,52.185,54.773,55.174,57.773,56.822,62.322,58.36,73.321,73.703,49.152,49.29],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"862b67674912aa8d134be72444f4cdb9a5b50c33276e2a90ef4ae87f302351a8","reconnectToLiveMs":7.272,"canonicalVerifierHash":"abd743722679bd1335b1944cd1a5e315a89097e014d9b43d57957012d766e41a","roundTripHash":"6954dfaff2a7993d6a4d77208f9bb004083a5722931341643974ed81212fa7e8","remoteConvergenceMs":8.062,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":30,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"222c0dc904bf299d49547394de05a001ecc68710bc02a1b311b4beafb0beb019","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[51.422,47.152,48.34,53.457,51.051,50.34,50.417,53.342,52.676,55.709,54.993,54.686,57.345,55.661,63.98,59.644,91.792,73.566,54.22,50.021],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"d0cd4491b844b50e85c3cbec56f3095662a981c0b8b695d7c85637b0e269aad1","reconnectToLiveMs":23.102,"canonicalVerifierHash":"1894c1445629a6547704f4dfd8bb17797b825eb585c520264c5c08dbc6366d77","roundTripHash":"bb887ac1fe5cae5ba0f2625c2af2b79493cb3fd885fdd8e1560a3aebe032b712","remoteConvergenceMs":8.524,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":31,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"c3760666249cd5b3679cb726fd60c15bde3e2a9bd079a7800fa297f824b7d339","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[51.915,49.157,49.165,52.792,54.015,53.038,52.171,54.662,51.894,51.815,54.904,53.953,57.294,55.846,62.601,60.022,73.284,73.928,53.398,48.064],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"206eaacdceff3df5532e76f3e5adc4964146e8504a64cbfddb43fabf4fce28ef","reconnectToLiveMs":9.788,"canonicalVerifierHash":"47d58b681022fd8ecc3b63238639a194262bef74422dd577da50fc761551d216","roundTripHash":"1ef7b56e658c8f1715f96246a583228c7b022d4e53b86374934a608bda3567b1","remoteConvergenceMs":7.995,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":32,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"f8a1efc44c616e628d61be595d4b092755c5da9e4347176fbf48421b326056c1","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.783,47.955,48.072,50.274,50.497,51.756,51.801,53.193,53.228,52.65,53.67,54.815,59.941,55.873,66.93,61.075,75.172,73.193,51.543,49.645],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"9f583fc4b098f02824bb5fa50c8ed9a76709c8850a94de973e02fe98003010ee","reconnectToLiveMs":57.18,"canonicalVerifierHash":"ef6d53a40b328a9729ca3c0157aa0adbe1c7cb390ab38e0c6949e03aa9a45147","roundTripHash":"b5389d9f11296e131badd2a6f0a299b125227ebc6b542936d07d8d3fe2b0c7d0","remoteConvergenceMs":8.252,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":33,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"53b09101de072e7f39e1f605b4e64c528f99951f9ec10abc33901a08def7d6ad","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[55.553,49.339,49.28,52.439,51.929,50.983,51.145,52.802,51.614,54.961,54.944,55.264,56.502,63.448,73.684,57.573,73.054,74.641,52.254,49.91],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"0c9c2a625372e9dea51750e620c118b2702bab4e7484530ad1bd6872097db785","reconnectToLiveMs":102.382,"canonicalVerifierHash":"495cf6e87b63acba4ec386a2f17165d796435465d45f391c4cf212781f7eca5b","roundTripHash":"7a43f69fdf62c9f83ba733e4b8a6488902860126dc015c5037cd8ea03da60bde","remoteConvergenceMs":8.348,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":34,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"41039af22db14b082c0f8d27d0f337109516f3c0ff5395bbdbd52c3f2a73e366","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[52.417,46.785,47.867,49.984,49.108,50.718,52.081,53.852,53.585,55.537,55.205,57.244,55.598,59.088,72.131,57.788,74.259,76.373,237.73,54.525],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"1a24ce7e3c28f5807015976bbfa3e712f6def2a8d3b8f8ed87dfb6d2903f154b","reconnectToLiveMs":87.245,"canonicalVerifierHash":"cf93ed2b3ec0f97763ae4ab9f60906018563a26a37da1abcd4ea47f5a3b27149","roundTripHash":"1fbeea46ef7e1780b7fe8db03d2cc35b54a8e469a4fd1047a159498bfb643161","remoteConvergenceMs":8.364,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":35,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"b9275a1393e700c6b85ebf43295ee9fbf7e0215d3e64d41b229fd4d0d1d16252","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[48.947,49.034,49.876,57.628,56.987,53.885,50.78,52.088,53.213,55.365,54.32,54.576,57.691,59.187,64.926,61.792,70.686,74.156,49.07,49.296],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"519d837de5b7f860793ea33c7269be33f2d3286b79c6e9e9507418df34faf035","reconnectToLiveMs":19.045,"canonicalVerifierHash":"887321746985c1c8bd0822300fe6373a4ef21380ef5ecc9c335f47e8cf9a8271","roundTripHash":"b9cea9c0d0df6e43effaaff688e7e8b7dcea72d7f88d91df5d70c9dcd51b3ece","remoteConvergenceMs":8.516,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":36,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"c0c7e0207a6bcd0957b602550570e42db42800fe57c66626010f25242d5f8208","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[51.72,47.037,49.46,51.551,49.767,50.85,52.254,52.484,51.625,52.109,53.737,54.589,56.407,57.635,71.458,58.872,70.89,73.648,49.386,48.391],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"44641ee26407719b887dc44016ae6afe719ff28a61bf2ac2adff13418a002f2d","reconnectToLiveMs":83.212,"canonicalVerifierHash":"b25494e6ef17124b508fe8d47b13d055664336898cf2192f495b01b2037d7c0e","roundTripHash":"d14c83263732d3d4ce5c1ddffa521f4a8e139a0115a0b13204c75c5d5ded723f","remoteConvergenceMs":8.517,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":37,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"78c97698051da2fb983461e5c8f6d2d3bd357afb10e6d42cf9918b98c8804b5a","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[52.038,50.216,49.314,51.184,49.308,50.59,52.238,52.357,59.577,56.476,53.832,55.869,56.96,56.562,61.491,57.635,72.316,74.246,51.115,47.622],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"36b5339d6a5239af0c2e64c5a19e64b084ad9ce6a27851969945465a67dbb365","reconnectToLiveMs":19.025,"canonicalVerifierHash":"c9bd2baab0265dc7a190e0cd455f13e2575f7a04ebe2a01afecfb5d78ea1cc65","roundTripHash":"6ddbcafed928297a094e02cca9d9201fd12c225dd55b7df8a267aadd5e671715","remoteConvergenceMs":8.072,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":38,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"3f2dafd0bc96122b9fd4274b15b67a6834f00aefcce0e9fd414dc56bee7ee67b","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.128,47.358,57.054,52.929,49.732,54.572,52.616,73.347,88.966,90.432,106.124,89.543,94.186,85.756,89.516,88.9,111.277,77.107,159.116,58.65],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"4df6eb45193bcf8a0edcab57b140e5e711a42313537c1b06ad7ac40495a6ed7d","reconnectToLiveMs":40.11,"canonicalVerifierHash":"dac23c9b615ba736f667822e98cd81583d5596d3eb635400fd7923a4b821f2fd","roundTripHash":"5f2b2d6429deabacf2274aa4cd918f601a4d7979d4e6425eac0dabff353df84e","remoteConvergenceMs":8.24,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":39,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"7b901f84dd62107f244be49a92208d42d8c9d3da353225163c2eae58533cc5e7","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[75.676,179.593,67.112,54.792,53.356,50.804,54.436,53.396,51.603,53.341,54.106,56.779,56.164,56.872,70.128,60.498,71.721,73.438,50.697,49.613],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"3a0e6a8d108e4259c6dacb8b38f8b350945dc7fa4e9364e3913d2caddffe18bd","reconnectToLiveMs":79.274,"canonicalVerifierHash":"1be630ac18ad34887058f68f246be9e0f8a79ae0b431e0342f77f0aafbbf60a6","roundTripHash":"010a6713bd4cb32e2f00459f2d042fbfe23920beb3dba219f9a975fd66606098","remoteConvergenceMs":7.995,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":40,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"cd40e9a07822696a4198119192a3961886f52674873dbed1fab915a6e50927d3","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[58.658,47.189,49.207,49.781,50.882,50.83,52.179,51.635,53.33,52.854,59.303,55.228,58.04,58.163,69.229,60.215,74.177,76.348,53.142,53.245],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"a91db8c869cb35510ed31cccd264a1874ddc908ddfb04db8e65f8148f5a91578","reconnectToLiveMs":70.271,"canonicalVerifierHash":"25122c18d674ea9d81866fefb9811438d5d0e6930edbb7b4f1bd1336987d3c84","roundTripHash":"f83c2ea15ce0f99cd935fd0aaeffe3bac0805ab6b6f1d45c6ba5e715257fbe02","remoteConvergenceMs":8.389,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":41,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"3c6017b5b1b61473fc4384d5c642f8c4f21fc498a2cba3981a4fcb04e8e8f815","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.535,50.453,52.33,52.467,50.524,51.755,53.871,53.392,52.601,55.377,56.604,58.993,58.145,61.613,72.18,60.587,72.989,75.687,52.332,49.186],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"f46fbceca36e6df65f09e8184dc171220da87ffa997e93edae4425e494a5644d","reconnectToLiveMs":86.319,"canonicalVerifierHash":"44e6edd99da52fe8e35e1513170aea2419cc5cb214cf506ff75a706440274ae3","roundTripHash":"519c1ac9267bb4369abd0b2bbc015623f2f8c3a7c58946522c36df8a6028a425","remoteConvergenceMs":8.44,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":42,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"2a3bb8f9e96bbff75da1c0bdcd21b3452ff8c8762013e2f6eca2ca641b707758","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[52.401,47.301,49.769,50.808,49.004,52.588,52.107,53.5,53.271,52.893,56.103,56.561,57.277,58.481,66.595,58.908,71.236,73.785,52.777,48.676],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"86043e9b248940531840e7cd18115beb2b0dddcf5dc9db88ab096d6ece3d17a4","reconnectToLiveMs":81.184,"canonicalVerifierHash":"94b10f4001fe9e25842dfeaa8246258d85efe73aa823a1f32a587317b43cc262","roundTripHash":"99affcac47d7e010b61d60b84890b99227a88f96e686afecc64ecc96e1a9e434","remoteConvergenceMs":8.461,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":43,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"3f8a10342efb6e53884d12c1e1425f2ec7ecdd96013bd9223c4475968eca8e3e","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[54.306,47.901,50.021,49.896,48.356,50.601,52.433,56.274,52.351,61.789,56.201,55.147,56.334,57.711,62.322,59.071,70.857,73.701,52.981,47.958],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"19c0e27e7c3cdf297feaa3b02930adcda8b97b4c388b33ea1769e9afdf522ba2","reconnectToLiveMs":31.06,"canonicalVerifierHash":"3f91d6fc35170cf6b81469cd10fdc2276151f41642c183954c905dc2fd72e913","roundTripHash":"2d5a3bcc5c59f2b66c302ee900ea418589d560867b9df6c4ad8433a1d8132683","remoteConvergenceMs":8.491,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":44,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"0eb474ca0c5f15b2c3bb2837e8ccb7540cc953610e19a90021b5d7fcc17d7e92","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.634,48.41,48.882,50.057,49.581,52.317,51.69,53.295,53.961,54.369,55.632,55.421,55.526,56.388,71.229,59.573,71.844,74.882,51.527,49.147],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"d296acd1d505cc41e1746ba8b4f51adf18849dbc0a28408dd5e3bf8e616d4672","reconnectToLiveMs":85.215,"canonicalVerifierHash":"45675064eb12cf1f1a91352bc3ec835026de943504172dfe66afe47547538963","roundTripHash":"db68375a5b6b0d90d87a15cb7be4f53702d3686f539bd3c6e4e91fe56aecf1f5","remoteConvergenceMs":10.166,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":45,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"48fb865b80110a4ec663a65f26f9bffa285ecb926bc3654aaac732c60d7b3b0a","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[58.571,54.422,54.35,57.046,56.453,57.909,55.084,57.663,54.816,60.273,55.275,58.253,61.867,57.962,70.439,61.782,72.682,75.279,67.517,50.186],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"129dec31e4e2f2823c59ae9b87fcae42d394591c61f5be778999c45da2a3e444","reconnectToLiveMs":71.228,"canonicalVerifierHash":"df97c65603a19f4c4351462abc77ddac04994d60de19108557822d5b7a571794","roundTripHash":"bd72d07f8cfa10ba473e751e8571d18cff71139e9de28b389a357005b0549039","remoteConvergenceMs":8.144,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":46,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"2547f5ef76c4962e0f352c8e03a471c880bc8ca5b6172b583e469813db94f886","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[57.297,49.602,48.728,51.819,52.78,51.647,52.095,53.299,52.682,56.697,60.222,59.316,57.049,58.813,63.12,59.483,72.977,74.498,51.085,49.826],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"50395ad41530e80d5559fc8b6af5507a8a393adec21dbaf5ef71718f035be3ef","reconnectToLiveMs":25.008,"canonicalVerifierHash":"9b4ee333fd4009d9b1edef10a8a06c285dae7b061b309176dac94fb8296f1af3","roundTripHash":"0574858639d852e4499b0d575c1ed16d92e54431275cbba318a6c50a5938a9f4","remoteConvergenceMs":8.292,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":47,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"74fe76755a9c32233697d6c69e3751a5a0429d237d306cfa1e17d9b51b4a8569","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[52.756,47.199,48.98,49.799,49.346,49.938,51.865,51.959,53.758,53.848,55.803,55.476,57.419,58.829,66.43,60.141,71.485,73.361,51.482,48.65],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"e3d92a8e2ae00349f3b3869363ede60248488f17094c62af9e2b17952177dc57","reconnectToLiveMs":48.063,"canonicalVerifierHash":"3c4caaedf4de1dfe50de5ca3aedad7fd6de3694157191abd14d816ba949321c5","roundTripHash":"f055ebed68cf1f028f3ac92152eb9804730bd979cbe5771187ea7d8a93f1b604","remoteConvergenceMs":7.988,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":48,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"34d1c2996c4711c53b654ccb9172bdd508ff7023bd0729c37305aff833a9e8c3","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[53.181,49.251,50.476,51.0,49.904,51.704,57.809,52.343,53.128,53.65,53.441,54.9,58.393,57.382,72.549,64.743,84.652,75.932,54.646,48.87],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"e7e8f860ed711b1ba4c898d3c6a3a7443cdcb343f3b965d64050f635bebb045a","reconnectToLiveMs":91.238,"canonicalVerifierHash":"882b979772b174022efc16c448d930c4c985077f663f74e281e64d2730ca2d9e","roundTripHash":"d800ff5ea7d263d57c07e1a252a14b4b8787f97831d2de8d8608dfa3a4187f23","remoteConvergenceMs":8.934,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":49,"adapter":"convex_flutter","operationCount":1000,"traceSha256":"cd10c09eb29b4c21d63fff22d70e92b231eb178d82ab60ea4f2dd0b671e6f0c5","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[52.343,46.916,51.398,51.697,49.817,49.45,51.962,52.53,54.376,54.323,55.072,54.702,56.073,56.094,73.688,58.779,71.976,73.063,52.839,49.83],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"8d2697434c6eed85928903a55f2524696d540ea4d20637d254aaa9ff692314fc","reconnectToLiveMs":100.349,"canonicalVerifierHash":"897c1e45d48565825398a418ad07d9557552005e11b69982d8979da0e6def54e","roundTripHash":"a93b4e6243f97268681c90698b8754de9e2cf2decf12fb6104cb376aa9ace707","remoteConvergenceMs":7.988,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10}]} \ No newline at end of file diff --git a/tool/convex_client_gauntlet/runtime/results/dartvex_correctness.json b/tool/convex_client_gauntlet/runtime/results/dartvex_correctness.json new file mode 100644 index 00000000..ec4b095f --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/results/dartvex_correctness.json @@ -0,0 +1 @@ +{"schemaVersion":1,"status":"passed","adapter":"dartvex","candidateVersion":"0.2.0","flutterRustBridgeVersion":null,"deployment":"local:127.0.0.1:3210","gitCommit":"fb83488c0924f8daf57c4bdfc48d7a4a5ff0c8f5","baseFixture":{"path":"test/fixtures/strategy_integrity/base-test-v43.ica","sha256":"8544873d608a0ad885b2e6042a383596a0b1dc37514034281b4e4eec6168756a"},"seedCount":50,"operationsPerSeed":1000,"totalOperations":50000,"allCanonicalEqual":true,"allResolved":true,"processRestartCheckpoint":true,"bytesSent":20224647,"bytesReceived":48149313,"wallClockMs":64346.84299999999,"maxRssBytes":256311296,"machine":{"operatingSystem":"macos","operatingSystemVersion":"Version 26.5.1 (Build 25F80)","processors":8,"dartVersion":"3.11.0 (stable) (Mon Feb 9 00:38:07 2026 -0800) on \"macos_arm64\""},"seeds":[{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":true,"exercised":true,"freshTokenAcceptedMs":10.026,"manualReconnectMs":144.884,"postReconnectAcceptedMs":155.986,"queuedBatchReplayedExactlyOnce":true,"reconnectCalled":true,"recoveryMs":156.006,"refreshSessionCalled":true,"rejectedTokenObserved":true,"tokenChanged":true},"batchLatencyMs":[42.608,35.148,34.734,35.035,34.922,36.206,35.541,37.066,37.501,36.019,65.184,38.437,38.517,38.294,55.611,38.978,48.253,48.317,32.231,30.517],"canonicalVerifierHash":"26d71e8df48fba1f7aae2c8cf4b5569e8b9360166b9bb6ed887ce7f14048f2f7","duplicateNoopHash":"2f5c83b805fd22471e866932f0ad3a6b438850368d79cbaada4e91a2306d36f8","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":10,"fault":"auth_reject_refresh"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"b4e244c38b89789f1191e988768aa681f4722184533077ef58eb6f95ee6a0e52","faults":{"authRefresh":true,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":156.184,"rejected":90,"remoteConvergenceMs":9.335,"retryCount":0,"roundTripHash":"57f03a40845f1cbcec927c4a2abc68781fcec79e10d5f8a896a7deb95bbd9d69","seed":0,"traceSha256":"ddf6d41ed9ccdbf3c60766fe6b0318218dd8954d8615fcffb2a8daefe849aa06","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[39.665,34.943,35.239,36.301,36.401,38.339,36.565,39.271,37.606,38.63,38.333,39.363,38.869,39.184,52.962,41.251,48.59,48.749,32.402,31.57],"canonicalVerifierHash":"521b140c0a612b8ea44a1d9b4b03cbf0573799ae00bf05498122bacab81c1bba","duplicateNoopHash":"f5cf17c272add3c1c7d3460a3f29c4ee4bf786dd0d24d00d5010fd3069450206","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":103.463,"rejected":90,"remoteConvergenceMs":8.175,"retryCount":0,"roundTripHash":"42034206a0ce276524b5f6c4b61270d036eafa3ea888f6af32fcc8898136425d","seed":1,"traceSha256":"74e1c135f93f0e9ebf7029a5222aaa38b6aa0f834e31861e9e3de8515922bca4","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[39.39,34.965,37.174,35.563,36.11,36.582,38.492,37.793,38.708,38.19,39.021,39.703,39.282,39.583,44.904,39.624,49.498,51.921,34.448,32.191],"canonicalVerifierHash":"9ca1893e49091976796548e8568b4e1791bff1f72f5f883892ef688a59483304","duplicateNoopHash":"f241cf2843f3adfa94f6566c00df83a1f234b4993b73daa1fe08ff8b34e89260","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":55.995,"rejected":90,"remoteConvergenceMs":8.3,"retryCount":0,"roundTripHash":"44c8e21bbcb1c01d01bd1446150e3c5e3daa0f256aced3da4e96ac1afeb6be93","seed":2,"traceSha256":"2b3e81b55c925a72176cd6f3d1515c7063d7706d8d4381c641b6fc0a8e121262","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[40.069,35.791,36.04,36.85,37.931,37.629,37.651,38.799,39.617,38.815,38.987,38.969,40.033,40.719,54.526,41.157,49.59,50.288,34.368,32.436],"canonicalVerifierHash":"197d1fc1c62be082b93dd2c51021a15c68e3f7551de5e0d949b11152f6ab6840","duplicateNoopHash":"1e34802380fb4c7b66dfe194d29cf6bb4a223e342c5ac58fb77bd10b9436b658","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":119.517,"rejected":90,"remoteConvergenceMs":7.963,"retryCount":0,"roundTripHash":"18bb3a755432638399f32b7fb39627dab2c7c474726dc01b433243d45e86ab8f","seed":3,"traceSha256":"f7105a396da40cd883ec4cc9e83f1a6cb53706e90e782cc3e71cb778bf252235","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[39.892,38.059,36.454,36.528,36.675,37.514,37.357,38.272,38.264,38.817,39.921,39.005,40.313,40.61,53.866,42.152,49.182,49.745,32.958,32.346],"canonicalVerifierHash":"472d3b636388ed4b3ce98242c337cdca98191dd93feeaf23b2f0bf72c2068232","duplicateNoopHash":"94cb107907f89f5fc3a85d810566e5f67ec06498ef9345e4236bce4c79443170","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":126.523,"rejected":90,"remoteConvergenceMs":7.589,"retryCount":0,"roundTripHash":"6bc869c98139d476a3b61de2ca80637ca4d956c2e99008462e6667110516d5e8","seed":4,"traceSha256":"f8cc2d86c82f00b22442e2b457b962f2e31d958be26bd26999c5035502d44d52","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[39.953,36.043,37.474,36.596,37.1,38.671,38.786,39.016,38.579,39.851,40.928,40.816,41.237,40.89,44.574,42.409,51.474,50.047,32.932,33.015],"canonicalVerifierHash":"60e119554dd2c84670ff85ad1f4743fe3b39bb2367a81ecfa4f978cd7e834be1","duplicateNoopHash":"69a9e28c6ba4259807f9135260188fb48763db7cd60da9f0bcbb2df689ac7fec","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":54.861,"rejected":90,"remoteConvergenceMs":7.427,"retryCount":0,"roundTripHash":"3e1eb93aba491b833fe657b72a49cb08871e51b6abd93ced58a16b4c1bb1d714","seed":5,"traceSha256":"d0e377de6c9acd5bde977020ce6b5a3b79f41ba792748eeb87617f0bbfe67073","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[42.123,36.347,36.803,37.735,37.933,37.726,40.935,39.849,39.641,39.264,39.902,40.177,42.264,40.966,58.928,42.26,50.693,56.274,34.561,33.006],"canonicalVerifierHash":"870bdc3fe241a36e4cafa9cd2db6550900db537148df0012129a4fc729168e44","duplicateNoopHash":"7b6d6f868d69e5c19a34b1ef7a5da5bd85c7b2c0de6b278023971d5125376186","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":138.784,"rejected":90,"remoteConvergenceMs":7.385,"retryCount":0,"roundTripHash":"a6d78c0173ff37ca01ce546bd371b25102dff424fc71e5ed158da8cc3afb9409","seed":6,"traceSha256":"4089e29162d4a1f4eeb277698a40e77dee654c5e6b024de882412828b2b868be","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[41.37,37.751,37.476,38.235,38.176,39.042,39.029,39.761,38.91,40.261,39.847,39.827,40.132,41.345,45.8,41.917,51.416,52.069,34.72,32.827],"canonicalVerifierHash":"07d665cbffdf875c025bb158a561350943ae3fbd5068762fd11819a6a4901942","duplicateNoopHash":"abc350378349c0c5ca8ac8718147350b24bba91d01f1b3584ac0ae8728392bbb","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":58.802,"rejected":90,"remoteConvergenceMs":7.699,"retryCount":0,"roundTripHash":"e9e701572c478eb4ff07bdc25b9686439e87f671b6912b55edab9f6640bd9348","seed":7,"traceSha256":"2f479b6ed6743dedae4a36d95c58c95f225ba529fcd863e5640f49afc5e838b7","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[42.317,36.767,38.726,40.325,38.393,40.051,39.546,42.167,40.5,41.073,40.149,40.285,41.872,40.868,54.99,42.863,51.482,52.294,34.939,33.781],"canonicalVerifierHash":"efc2b0c123c804ed7f1847b3cfaec274bad1ea5ae14b606f04cc0a565458437f","duplicateNoopHash":"7c4a10f6a44922af7b90a06f9e3796d4915b37934f1958e659c352ef988032a7","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":141.639,"rejected":90,"remoteConvergenceMs":7.902,"retryCount":0,"roundTripHash":"2381d28c5762232a05a96531e03aabc0b196671f5f4c493cfb8a2bc1e16fed87","seed":8,"traceSha256":"18d04c6a59b74a9026ab9d6f69161a1ae3101de4fe16acc08b831a885a6a301f","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[42.266,37.341,38.486,40.63,38.659,39.783,39.125,41.659,40.475,42.183,41.183,41.624,43.165,43.194,48.156,43.184,52.743,52.633,36.585,32.426],"canonicalVerifierHash":"703442e3b93e7bc929da059e86ab44de647f270cefc120656f501966b6847100","duplicateNoopHash":"acb46046274da0c9262f4910b29e4fda0a2bfe3132374a35db02c9f2f89398f4","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":61.885,"rejected":90,"remoteConvergenceMs":8.022,"retryCount":0,"roundTripHash":"e63096e423e4fa5de04d55dd992a191a939e288f22077b5dba9f1f30d0312ac0","seed":9,"traceSha256":"10e7fe955f927f3626b5586eff07c5c452e7dd8fada065f6ea12839907ebfb8f","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[41.745,38.55,39.82,40.531,38.961,41.076,39.428,41.153,40.38,41.587,41.377,42.583,42.521,43.181,60.903,43.214,51.966,52.231,35.256,34.619],"canonicalVerifierHash":"0dedd28fa52610998313b57e5e621be6488408b8475b8082434152a7e4bfffde","duplicateNoopHash":"68de0d932dda40e9fa94ad87bce5062194f4b64d032743224b09543a35fadb04","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":137.639,"rejected":90,"remoteConvergenceMs":7.721,"retryCount":0,"roundTripHash":"e199bf3ee7ce8299f481c487ecb563b9bc5a22508107847ee32280f5af2799fa","seed":10,"traceSha256":"3aedbcc55f63d2e40d1053713da07189c675eeedafc45557893bfb44a4a4a3fa","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[44.99,38.3,39.078,40.912,38.761,40.816,39.776,42.124,40.569,41.758,40.988,41.695,43.061,43.269,62.173,44.115,51.016,53.068,34.685,34.861],"canonicalVerifierHash":"976df9bc169579795add78059b22aecb15add1a77fa132a762b81256d7a14ab9","duplicateNoopHash":"ecd01e95d0ba98e4c1033dd1c11ae805f94ea9c57caa7eeb3883adbdba18baa9","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":146.874,"rejected":90,"remoteConvergenceMs":7.622,"retryCount":0,"roundTripHash":"6b2a4c8cae985614914a1122c691c7976d2f5fe6bc30dca6a2a54e7a718ebbdb","seed":11,"traceSha256":"d64e19b329a28294c7e145d5eea4d659b86b00fdc478f7472e57f1b93de378ef","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[43.405,37.692,37.907,38.705,38.575,41.526,39.321,41.76,40.252,42.711,40.769,41.976,42.062,43.448,62.286,44.689,52.252,52.089,35.443,35.083],"canonicalVerifierHash":"fe27618043429656abed9f1810fd3f7fe21fa7844cfe474e4ee27170aa6ba761","duplicateNoopHash":"75cb591df89ce4872e5d71150982ccb110460036da1a031d13af69b1a497bf44","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":144.977,"rejected":90,"remoteConvergenceMs":7.633,"retryCount":0,"roundTripHash":"08db87f541a6ad80969ae614cb3c192ee4668e76d650a3c3c83ae2205d7cae2f","seed":12,"traceSha256":"56270e40851c311cafe2638b7bdf9e162ca4190285215f3a788c34ab68e9fbfa","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[44.463,38.943,41.496,39.556,39.86,41.869,40.275,43.486,40.467,42.175,40.429,43.376,43.666,43.833,59.158,45.088,53.503,51.643,35.534,35.471],"canonicalVerifierHash":"1eb6c99489819977b8c62d4ac7cc2b6332253eea89c0028a0d00f543ccc80b4e","duplicateNoopHash":"a2780357577e00a64a3d6882842034f3c7a33a7fb73f76a1bb513582adc82f48","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":117.484,"rejected":90,"remoteConvergenceMs":7.656,"retryCount":0,"roundTripHash":"30a8267dc6ce65b39cf9e2f8e9d47c425c22d34ae75df7a6427dcfbf320b49ca","seed":13,"traceSha256":"04fbda33461bbb975b9e66fc1ff0fe176931fdbd9c56fe8d3b9f9abcc939ab07","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[42.967,38.415,39.008,42.222,40.17,41.074,39.967,42.243,40.277,41.792,42.81,42.716,42.0,45.339,69.81,45.81,53.544,53.172,34.924,35.053],"canonicalVerifierHash":"9f4f93499745ce94e2ea84804d41cdd24007491f9ad333f6994c23adaca1a97f","duplicateNoopHash":"6a79d43b71c80e3ece66ab93693e6111df9ac8633c5e8af7dfbcdc86f73f16e9","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":154.188,"rejected":90,"remoteConvergenceMs":7.941,"retryCount":0,"roundTripHash":"4406199eb581980a79d880cbb483fd97b1ac2b9ecf927ac3d48a1ede8f3a8ca6","seed":14,"traceSha256":"d105bcc5f7eaa8a768bd666c3d6fb072fb97a0191a935d70e688d086e52f95c4","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[44.663,39.379,39.698,40.552,39.281,41.158,39.782,42.725,39.955,42.478,42.702,44.346,43.69,44.168,64.574,46.865,53.618,53.677,36.153,35.789],"canonicalVerifierHash":"e44921495d13f7e764fb5170d992c6806add23c1ee2a902d2ed586e78b6518f2","duplicateNoopHash":"89f952d57ff5c744f32966f233f39fdf76ff3729bca2ffb22da379572bc772cb","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":146.749,"rejected":90,"remoteConvergenceMs":7.921,"retryCount":0,"roundTripHash":"885dcea93a305a0527b65c09ab8a51e42eeab8486b0a17a812adfc8a23dd0692","seed":15,"traceSha256":"fd2509f037ad595831e5af5de358a045a4944c5070aeccceca9a85e5d93f0dcb","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[43.38,38.794,39.073,40.913,40.511,40.974,40.851,42.871,41.416,41.948,42.445,42.663,44.113,43.485,51.903,43.297,52.953,53.62,36.206,33.96],"canonicalVerifierHash":"fcab45e94cbb1dd32f40022c4f2538d8a8921f6f9a993c44da5a1af856273521","duplicateNoopHash":"1a2c13daad7b021d5bf7aba90696fb2fba2a5479ff1fa49027c88e3b5e9bf237","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":79.007,"rejected":90,"remoteConvergenceMs":7.58,"retryCount":0,"roundTripHash":"23079740e8c81056d542e80219368de85f2c9ae8740e9db95182ac2d1433a5cf","seed":16,"traceSha256":"15974b2b6d314a57e29fe393296085940f83539b4485eb9ada849149db7a99cc","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[53.581,43.208,41.272,44.059,41.826,40.468,42.165,186.644,52.932,48.933,46.565,44.732,45.943,47.413,53.895,45.598,51.81,52.493,36.281,35.466],"canonicalVerifierHash":"0ae80e9274783e32170474a495f3c31bfa702e9acbe970cb1a04c439846eb86b","duplicateNoopHash":"834e79323bda37030a4c6af90b9a0dadade53242cd9801ba090d12a86054e645","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":119.19,"rejected":90,"remoteConvergenceMs":7.705,"retryCount":0,"roundTripHash":"5ebbea09bcb5c13323f4a6dea81b146de93f7fa134b025b2163ca48c62222abc","seed":17,"traceSha256":"53cce4ffa3236c18592a997edd684f10596e8cdceacd8402d858c284a7d9054d","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[44.512,39.675,40.826,41.641,41.438,40.77,42.268,41.604,41.055,43.233,42.76,43.316,43.307,42.525,58.776,45.112,53.329,53.389,37.699,34.375],"canonicalVerifierHash":"f3fd473e5c7029efed7f8b2b39933c0240a36cf83a5eb3286adfe377c9890da5","duplicateNoopHash":"2328c71bfb35d1fc60079b4c3572d96fac5e48f92fa4fee9baa2f9a469aa7404","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":134.581,"rejected":90,"remoteConvergenceMs":7.89,"retryCount":0,"roundTripHash":"9f8e4e09c1123b52138e32282eeb53f1d13c8317d8f3b2cda0cb11c576704704","seed":18,"traceSha256":"a542c21db854c18f4cdcb385e6ac298bf7af6282c0972913cc8fce70aec86847","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[42.409,38.809,39.556,40.558,39.845,40.737,41.289,43.095,42.66,42.694,42.985,43.922,43.625,43.914,61.152,44.398,52.414,52.745,35.473,34.026],"canonicalVerifierHash":"63e156ef82374f25fc17ac38cd7fb0ffa365fa4d6fcd29f42be9a52f729ab158","duplicateNoopHash":"1121a351ad1dd9adce4685ffd6e719a32e98c70594e4a09f928d6f996e9d1207","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":138.682,"rejected":90,"remoteConvergenceMs":7.692,"retryCount":0,"roundTripHash":"3bf2bcb94fab05718ea34aed383efbf9e411b10d95a6179ae911f90d6a57cb1a","seed":19,"traceSha256":"0d92405289eea8b6bef336eab7f6bd29373c9dfcab013fe1126707d0fc4cb0a6","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[45.267,39.573,38.696,40.33,39.295,40.839,38.926,42.327,40.62,41.736,43.308,43.073,43.327,45.236,49.482,43.129,52.184,51.988,36.14,34.365],"canonicalVerifierHash":"c8bdffb3ea12e788eb2a5088294f17e8705320e3c734ca0302d660b841565e56","duplicateNoopHash":"661be1cf36a3e6dfd57dfc2f78cfaaa70d9b9adf2badc7d67b4c20ac4a528c24","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":65.988,"rejected":90,"remoteConvergenceMs":7.981,"retryCount":0,"roundTripHash":"268efccad0ace19c28dc92d9c54b320c38ac129eae3a5b5f6710d15562fa7f3a","seed":20,"traceSha256":"810f5993000813418216c7c55f6a64a15516e1b6f63e77e55dc9d0beb326a59c","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[40.99,39.316,40.747,41.417,41.389,40.996,41.297,45.814,41.508,43.889,42.97,43.136,47.003,46.298,55.177,45.932,54.098,54.685,47.453,36.333],"canonicalVerifierHash":"c26d7da5ac7f287f6edff0b7633a49ceba13692f2421bf0738cd6bb044c5428e","duplicateNoopHash":"e1ca213dd003e35e7e8cb0e94b3cc3c65a6284240156492d42496a39c0216155","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":82.961,"rejected":90,"remoteConvergenceMs":7.602,"retryCount":0,"roundTripHash":"4d284bde91877dd19f45ec5357c0bbc3213b125ea5a2ab460aac161c834e56d1","seed":21,"traceSha256":"00514f57b0533bafeeb8cef85306163b05977fd156fb8d4f6e8971f43cd5c3d9","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[58.337,40.69,39.773,41.806,40.999,39.957,41.438,43.177,42.941,43.137,42.72,43.388,43.548,50.906,64.154,48.256,61.066,60.984,42.614,35.825],"canonicalVerifierHash":"7dc98e0042f910e161dc48692e61b71bee6f5f3c4e2c185947bcd346651c62c1","duplicateNoopHash":"0c84e6481a015a570d0181acae33fe459537c904b339fab12049bfcb9f98024a","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":129.992,"rejected":90,"remoteConvergenceMs":8.282,"retryCount":0,"roundTripHash":"8af6d9079e0d0da10a62772b0ec081dc064e6480c6545292713e51b5ee9afeda","seed":22,"traceSha256":"3b219f2a009454e6a7e145c270ea4540712ac42a2e64a3de49decb95d00cb6ec","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[48.029,41.083,42.59,43.682,43.026,41.77,42.796,43.43,42.992,42.906,43.135,43.328,42.783,43.114,56.152,43.677,53.926,53.75,37.53,35.939],"canonicalVerifierHash":"1f42cdfc51ff0c4a3d156d71d7af4941eda5ef046969f200459a8dfcf0df4594","duplicateNoopHash":"4a4946b0e8eb9183e6b90e464571b002e3053e95ed6510dcb24e619868058604","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":100.084,"rejected":90,"remoteConvergenceMs":7.57,"retryCount":0,"roundTripHash":"d9eb0ae47ebe116de7ffe955c9782bbc6cf6f1bacb1ff2cb44ad3528939d70d6","seed":23,"traceSha256":"a44400c0327fe034c8e1d489d65d4a1e8444a312806565991f9f90c2e7bd2427","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[43.975,40.274,40.137,42.041,42.96,41.152,40.473,41.993,41.711,42.393,42.968,43.412,43.675,43.044,58.421,43.522,52.493,55.279,35.54,35.796],"canonicalVerifierHash":"77478c60c3ae9cd45fc8dcda6a71cd0010be62120387e64242c60b1eb7c87a4e","duplicateNoopHash":"089b4218683a43b11f6a2fb52d4b660cd1db966fe14a98a652aba04a95c23a78","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"}],"faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":false,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"finalElementCount":82,"finalLineupCount":10,"finalPageCount":2,"finalStrategyRevision":15,"operationCount":1000,"reconnectToLiveMs":116.908,"rejected":90,"remoteConvergenceMs":9.145,"retryCount":0,"roundTripHash":"0001e26052bdd0bab5724856e8de207155cb84a9d448d7aef7c173ba4e4a96e3","seed":24,"traceSha256":"dbd479d2d15a8febd16d5f86b91f7273d4c05d57076b180f1daa3d318d89b39a","unresolved":0},{"acknowledged":910,"adapter":"dartvex","auth":{"acceptedAfterRefresh":false,"exercised":false,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"queuedBatchReplayedExactlyOnce":false,"reconnectCalled":false,"recoveryMs":null,"refreshSessionCalled":false,"rejectedTokenObserved":false,"tokenChanged":false},"batchLatencyMs":[46.194,39.016,41.269,40.621,40.714,41.011,40.783,42.786,41.292,41.513,56.841,50.088,52.021,49.673,53.765,45.56,55.483,57.132,44.183,35.354],"duplicateNoopHash":"610676792608b1dd1f2e06f6dc22864d82873dd8f8cbe86ef906a8174a708d0c","faultSchedule":[{"beforeBatch":0,"fault":"offline_queue"},{"beforeBatch":3,"fault":"delay","milliseconds":25},{"afterBatch":4,"fault":"duplicate"},{"beforeBatch":7,"fault":"subscription_restart"},{"beforeBatch":14,"fault":"reconnect"},{"afterOperation":500,"fault":"process_restart"}],"faultScheduleSha256":"d680df6635276d5593cea5cf988ac54915d8810167aae2037195424564f25e44","faults":{"authRefresh":false,"boundedRetries":true,"delayedDelivery":true,"deleteRecreate":true,"duplicatedDelivery":true,"offlineQueuedEdits":true,"processRestart":true,"reconnect":true,"revisionConflict":true,"subscriptionRestart":true},"operationCount":1000,"rejected":90,"retryCount":0,"seed":25,"traceSha256":"22a209a7f9723602fadb0c3baab2d73b8a7cef893297ade4cd87a2d61122cd5b","unresolved":0,"reconnectToLiveMs":101.576,"canonicalVerifierHash":"0b7840fc3e7157c541d7862e398acaa57ac57628f918e516746483093abf6745","roundTripHash":"44ae786ea3c9385ee3d3d5321fd63b6222cca1ea07240a307559cdbbbdbecc19","remoteConvergenceMs":16.915,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":26,"adapter":"dartvex","operationCount":1000,"traceSha256":"9c530a068ba1382fbe34f6b3bd558672f8f4538963b8ad673e4be834103e93bd","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[46.234,41.63,40.936,42.48,41.607,41.334,43.58,44.372,42.697,42.725,42.979,43.908,42.339,44.072,57.92,44.638,54.499,54.336,37.77,36.422],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"119ebe1f480b6c9f5df8f95cab240265bd09233c3f4b5dcd923728ef667b7a16","reconnectToLiveMs":129.001,"canonicalVerifierHash":"b8fc7d69a834b62d4a614de02cebbde60f3a9ccb93b4bec1f27e9987beced0ce","roundTripHash":"748fe55957a8fe14588430c1b62e80c9bf9bb046656f3dcd48d1691f1b33b54b","remoteConvergenceMs":8.488,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":27,"adapter":"dartvex","operationCount":1000,"traceSha256":"0135b2eafc700e90fcbcd47b19e5a1d34ab5e99f255b4b315e2161c2896ef910","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.256,39.124,39.475,40.925,39.882,41.26,40.733,43.702,46.276,43.669,46.007,49.697,55.305,45.26,64.296,45.285,54.843,53.42,37.169,35.085],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"4ad42017a48dbc8e12d942d945792c060603e08d9b7d18872b2158673ad23d7f","reconnectToLiveMs":154.689,"canonicalVerifierHash":"16a2f4cfba0166cf6435be3b64f11efbcaa06678f991140dbacc8d4e32097661","roundTripHash":"3c1ab0d073cd7610b4cd4a7c6521c0b5e47e4534104feda430f35e148f39f367","remoteConvergenceMs":8.011,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":28,"adapter":"dartvex","operationCount":1000,"traceSha256":"03fd5bbcfd5789fb329015b4ca3015f10c5322f782f41d1a33c8739dcc250103","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.072,39.965,40.369,42.355,41.703,42.536,41.977,43.73,43.306,42.655,42.316,42.871,45.222,43.211,64.956,46.046,52.974,54.584,36.259,35.483],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"2d62e883551de7eef0494e8e419eb251320287cfd85221fa771a2ec7467bb810","reconnectToLiveMs":154.186,"canonicalVerifierHash":"b990e106461c9e852b53e3be3e6c612b61f79bacc9e5c999f8ef1de308a108a0","roundTripHash":"8231af29d3d0c7601470ec0da754f2f0fded387c6bbda084509076505efb0e9b","remoteConvergenceMs":8.059,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":29,"adapter":"dartvex","operationCount":1000,"traceSha256":"8c898c274aa218bfce9e6a4f758d9273e1401b0edeab0db3d648e233aef3c687","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.497,39.878,40.155,41.975,40.596,41.654,41.632,44.082,42.082,41.78,41.322,42.451,42.689,42.984,56.875,49.585,56.771,55.375,41.507,38.663],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"862b67674912aa8d134be72444f4cdb9a5b50c33276e2a90ef4ae87f302351a8","reconnectToLiveMs":78.069,"canonicalVerifierHash":"abd743722679bd1335b1944cd1a5e315a89097e014d9b43d57957012d766e41a","roundTripHash":"6954dfaff2a7993d6a4d77208f9bb004083a5722931341643974ed81212fa7e8","remoteConvergenceMs":8.04,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":30,"adapter":"dartvex","operationCount":1000,"traceSha256":"222c0dc904bf299d49547394de05a001ecc68710bc02a1b311b4beafb0beb019","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.681,40.799,40.974,40.953,40.552,40.607,41.857,41.8,42.629,41.38,40.858,43.434,42.728,43.527,54.171,44.274,54.212,53.196,36.127,34.308],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"d0cd4491b844b50e85c3cbec56f3095662a981c0b8b695d7c85637b0e269aad1","reconnectToLiveMs":99.276,"canonicalVerifierHash":"1894c1445629a6547704f4dfd8bb17797b825eb585c520264c5c08dbc6366d77","roundTripHash":"bb887ac1fe5cae5ba0f2625c2af2b79493cb3fd885fdd8e1560a3aebe032b712","remoteConvergenceMs":8.045,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":31,"adapter":"dartvex","operationCount":1000,"traceSha256":"c3760666249cd5b3679cb726fd60c15bde3e2a9bd079a7800fa297f824b7d339","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[43.298,38.959,38.382,41.848,42.344,40.326,40.346,44.686,42.335,42.64,41.521,41.554,44.202,43.741,51.251,44.482,54.085,54.681,39.005,35.885],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"206eaacdceff3df5532e76f3e5adc4964146e8504a64cbfddb43fabf4fce28ef","reconnectToLiveMs":75.035,"canonicalVerifierHash":"47d58b681022fd8ecc3b63238639a194262bef74422dd577da50fc761551d216","roundTripHash":"1ef7b56e658c8f1715f96246a583228c7b022d4e53b86374934a608bda3567b1","remoteConvergenceMs":7.786,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":32,"adapter":"dartvex","operationCount":1000,"traceSha256":"f8a1efc44c616e628d61be595d4b092755c5da9e4347176fbf48421b326056c1","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.09,45.687,46.955,43.621,40.107,41.113,40.637,41.248,41.798,43.484,42.107,41.891,43.468,43.699,52.351,44.981,53.788,56.743,36.665,35.184],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"9f583fc4b098f02824bb5fa50c8ed9a76709c8850a94de973e02fe98003010ee","reconnectToLiveMs":75.109,"canonicalVerifierHash":"ef6d53a40b328a9729ca3c0157aa0adbe1c7cb390ab38e0c6949e03aa9a45147","roundTripHash":"b5389d9f11296e131badd2a6f0a299b125227ebc6b542936d07d8d3fe2b0c7d0","remoteConvergenceMs":7.737,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":33,"adapter":"dartvex","operationCount":1000,"traceSha256":"53b09101de072e7f39e1f605b4e64c528f99951f9ec10abc33901a08def7d6ad","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.435,39.798,41.407,47.951,45.777,40.732,43.537,44.461,43.111,43.657,43.306,43.956,43.872,43.327,56.297,44.881,53.069,55.16,37.253,35.813],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"0c9c2a625372e9dea51750e620c118b2702bab4e7484530ad1bd6872097db785","reconnectToLiveMs":104.226,"canonicalVerifierHash":"495cf6e87b63acba4ec386a2f17165d796435465d45f391c4cf212781f7eca5b","roundTripHash":"7a43f69fdf62c9f83ba733e4b8a6488902860126dc015c5037cd8ea03da60bde","remoteConvergenceMs":7.778,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":34,"adapter":"dartvex","operationCount":1000,"traceSha256":"41039af22db14b082c0f8d27d0f337109516f3c0ff5395bbdbd52c3f2a73e366","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[47.095,41.301,40.825,42.249,39.634,40.897,42.579,44.226,42.129,42.067,42.734,42.967,43.779,43.724,57.158,44.567,56.615,55.017,39.129,36.504],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"1a24ce7e3c28f5807015976bbfa3e712f6def2a8d3b8f8ed87dfb6d2903f154b","reconnectToLiveMs":106.293,"canonicalVerifierHash":"cf93ed2b3ec0f97763ae4ab9f60906018563a26a37da1abcd4ea47f5a3b27149","roundTripHash":"1fbeea46ef7e1780b7fe8db03d2cc35b54a8e469a4fd1047a159498bfb643161","remoteConvergenceMs":7.73,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":35,"adapter":"dartvex","operationCount":1000,"traceSha256":"b9275a1393e700c6b85ebf43295ee9fbf7e0215d3e64d41b229fd4d0d1d16252","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.49,49.443,46.883,47.815,40.734,46.451,47.456,44.131,42.486,46.913,43.587,45.317,43.465,47.334,58.984,44.185,56.206,54.582,39.856,36.529],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"519d837de5b7f860793ea33c7269be33f2d3286b79c6e9e9507418df34faf035","reconnectToLiveMs":121.464,"canonicalVerifierHash":"887321746985c1c8bd0822300fe6373a4ef21380ef5ecc9c335f47e8cf9a8271","roundTripHash":"b9cea9c0d0df6e43effaaff688e7e8b7dcea72d7f88d91df5d70c9dcd51b3ece","remoteConvergenceMs":7.922,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":36,"adapter":"dartvex","operationCount":1000,"traceSha256":"c0c7e0207a6bcd0957b602550570e42db42800fe57c66626010f25242d5f8208","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.719,42.506,41.162,39.761,39.392,42.228,40.406,41.543,40.291,42.503,43.617,42.669,42.278,45.436,58.196,45.858,56.098,53.851,39.156,36.764],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"44641ee26407719b887dc44016ae6afe719ff28a61bf2ac2adff13418a002f2d","reconnectToLiveMs":109.798,"canonicalVerifierHash":"b25494e6ef17124b508fe8d47b13d055664336898cf2192f495b01b2037d7c0e","roundTripHash":"d14c83263732d3d4ce5c1ddffa521f4a8e139a0115a0b13204c75c5d5ded723f","remoteConvergenceMs":7.631,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":37,"adapter":"dartvex","operationCount":1000,"traceSha256":"78c97698051da2fb983461e5c8f6d2d3bd357afb10e6d42cf9918b98c8804b5a","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.166,41.817,41.982,42.094,39.899,42.726,40.359,43.87,42.42,41.931,42.577,43.107,44.635,45.323,54.738,45.525,56.661,55.694,36.087,35.17],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"36b5339d6a5239af0c2e64c5a19e64b084ad9ce6a27851969945465a67dbb365","reconnectToLiveMs":83.358,"canonicalVerifierHash":"c9bd2baab0265dc7a190e0cd455f13e2575f7a04ebe2a01afecfb5d78ea1cc65","roundTripHash":"6ddbcafed928297a094e02cca9d9201fd12c225dd55b7df8a267aadd5e671715","remoteConvergenceMs":7.633,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":38,"adapter":"dartvex","operationCount":1000,"traceSha256":"3f2dafd0bc96122b9fd4274b15b67a6834f00aefcce0e9fd414dc56bee7ee67b","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.932,40.228,40.432,41.801,41.63,42.562,42.729,49.707,51.857,43.805,43.119,42.909,43.017,44.312,55.097,43.811,55.0,54.319,38.159,35.281],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"4df6eb45193bcf8a0edcab57b140e5e711a42313537c1b06ad7ac40495a6ed7d","reconnectToLiveMs":91.093,"canonicalVerifierHash":"dac23c9b615ba736f667822e98cd81583d5596d3eb635400fd7923a4b821f2fd","roundTripHash":"5f2b2d6429deabacf2274aa4cd918f601a4d7979d4e6425eac0dabff353df84e","remoteConvergenceMs":7.942,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":39,"adapter":"dartvex","operationCount":1000,"traceSha256":"7b901f84dd62107f244be49a92208d42d8c9d3da353225163c2eae58533cc5e7","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.697,39.106,41.403,42.077,41.539,42.12,42.391,43.699,42.01,62.751,42.841,43.335,42.253,43.608,61.863,46.594,55.329,53.858,36.831,36.038],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"3a0e6a8d108e4259c6dacb8b38f8b350945dc7fa4e9364e3913d2caddffe18bd","reconnectToLiveMs":135.649,"canonicalVerifierHash":"1be630ac18ad34887058f68f246be9e0f8a79ae0b431e0342f77f0aafbbf60a6","roundTripHash":"010a6713bd4cb32e2f00459f2d042fbfe23920beb3dba219f9a975fd66606098","remoteConvergenceMs":7.869,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":40,"adapter":"dartvex","operationCount":1000,"traceSha256":"cd40e9a07822696a4198119192a3961886f52674873dbed1fab915a6e50927d3","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[43.365,40.04,39.822,42.147,40.44,41.77,41.642,42.799,43.771,42.327,41.826,42.524,43.756,43.561,55.4,45.837,56.339,54.431,36.703,35.129],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"a91db8c869cb35510ed31cccd264a1874ddc908ddfb04db8e65f8148f5a91578","reconnectToLiveMs":104.052,"canonicalVerifierHash":"25122c18d674ea9d81866fefb9811438d5d0e6930edbb7b4f1bd1336987d3c84","roundTripHash":"f83c2ea15ce0f99cd935fd0aaeffe3bac0805ab6b6f1d45c6ba5e715257fbe02","remoteConvergenceMs":7.952,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":41,"adapter":"dartvex","operationCount":1000,"traceSha256":"3c6017b5b1b61473fc4384d5c642f8c4f21fc498a2cba3981a4fcb04e8e8f815","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[46.122,41.241,40.143,197.297,42.51,44.375,43.146,45.811,46.608,43.548,43.023,43.926,43.684,43.621,61.45,43.919,53.558,54.785,38.385,36.568],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"f46fbceca36e6df65f09e8184dc171220da87ffa997e93edae4425e494a5644d","reconnectToLiveMs":130.688,"canonicalVerifierHash":"44e6edd99da52fe8e35e1513170aea2419cc5cb214cf506ff75a706440274ae3","roundTripHash":"519c1ac9267bb4369abd0b2bbc015623f2f8c3a7c58946522c36df8a6028a425","remoteConvergenceMs":7.933,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":42,"adapter":"dartvex","operationCount":1000,"traceSha256":"2a3bb8f9e96bbff75da1c0bdcd21b3452ff8c8762013e2f6eca2ca641b707758","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[45.988,44.689,46.071,42.675,42.725,43.834,43.538,45.137,43.46,43.332,42.474,44.537,44.883,45.693,57.094,47.814,56.171,56.62,39.951,35.788],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"86043e9b248940531840e7cd18115beb2b0dddcf5dc9db88ab096d6ece3d17a4","reconnectToLiveMs":95.254,"canonicalVerifierHash":"94b10f4001fe9e25842dfeaa8246258d85efe73aa823a1f32a587317b43cc262","roundTripHash":"99affcac47d7e010b61d60b84890b99227a88f96e686afecc64ecc96e1a9e434","remoteConvergenceMs":7.902,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":43,"adapter":"dartvex","operationCount":1000,"traceSha256":"3f8a10342efb6e53884d12c1e1425f2ec7ecdd96013bd9223c4475968eca8e3e","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[41.953,41.659,47.41,44.927,43.958,46.428,42.175,44.625,43.337,42.318,46.276,43.325,45.741,46.238,54.809,49.586,55.04,54.784,42.1,37.152],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"19c0e27e7c3cdf297feaa3b02930adcda8b97b4c388b33ea1769e9afdf522ba2","reconnectToLiveMs":95.048,"canonicalVerifierHash":"3f91d6fc35170cf6b81469cd10fdc2276151f41642c183954c905dc2fd72e913","roundTripHash":"2d5a3bcc5c59f2b66c302ee900ea418589d560867b9df6c4ad8433a1d8132683","remoteConvergenceMs":8.229,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":44,"adapter":"dartvex","operationCount":1000,"traceSha256":"0eb474ca0c5f15b2c3bb2837e8ccb7540cc953610e19a90021b5d7fcc17d7e92","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[43.793,37.782,45.4,46.95,45.749,41.041,40.945,42.482,45.489,42.804,43.291,41.962,43.043,44.073,60.594,51.917,58.633,55.131,36.915,35.685],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"d296acd1d505cc41e1746ba8b4f51adf18849dbc0a28408dd5e3bf8e616d4672","reconnectToLiveMs":54.978,"canonicalVerifierHash":"45675064eb12cf1f1a91352bc3ec835026de943504172dfe66afe47547538963","roundTripHash":"db68375a5b6b0d90d87a15cb7be4f53702d3686f539bd3c6e4e91fe56aecf1f5","remoteConvergenceMs":7.928,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":45,"adapter":"dartvex","operationCount":1000,"traceSha256":"48fb865b80110a4ec663a65f26f9bffa285ecb926bc3654aaac732c60d7b3b0a","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[43.179,38.392,41.448,44.172,40.114,42.252,40.848,41.86,42.588,42.784,43.368,43.853,45.837,43.065,47.103,47.753,55.706,54.495,39.499,35.883],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"129dec31e4e2f2823c59ae9b87fcae42d394591c61f5be778999c45da2a3e444","reconnectToLiveMs":56.787,"canonicalVerifierHash":"df97c65603a19f4c4351462abc77ddac04994d60de19108557822d5b7a571794","roundTripHash":"bd72d07f8cfa10ba473e751e8571d18cff71139e9de28b389a357005b0549039","remoteConvergenceMs":7.867,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":46,"adapter":"dartvex","operationCount":1000,"traceSha256":"2547f5ef76c4962e0f352c8e03a471c880bc8ca5b6172b583e469813db94f886","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[44.541,39.292,41.459,42.365,40.538,41.621,42.414,42.775,42.741,42.727,42.908,43.436,45.105,43.832,63.242,45.977,54.766,56.069,38.811,35.679],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"50395ad41530e80d5559fc8b6af5507a8a393adec21dbaf5ef71718f035be3ef","reconnectToLiveMs":139.178,"canonicalVerifierHash":"9b4ee333fd4009d9b1edef10a8a06c285dae7b061b309176dac94fb8296f1af3","roundTripHash":"0574858639d852e4499b0d575c1ed16d92e54431275cbba318a6c50a5938a9f4","remoteConvergenceMs":7.791,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":47,"adapter":"dartvex","operationCount":1000,"traceSha256":"74fe76755a9c32233697d6c69e3751a5a0429d237d306cfa1e17d9b51b4a8569","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[43.563,42.675,41.15,44.006,43.016,42.782,42.187,42.73,41.725,43.513,46.287,47.532,44.004,44.117,53.092,45.778,53.654,54.411,40.029,35.629],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"e3d92a8e2ae00349f3b3869363ede60248488f17094c62af9e2b17952177dc57","reconnectToLiveMs":67.892,"canonicalVerifierHash":"3c4caaedf4de1dfe50de5ca3aedad7fd6de3694157191abd14d816ba949321c5","roundTripHash":"f055ebed68cf1f028f3ac92152eb9804730bd979cbe5771187ea7d8a93f1b604","remoteConvergenceMs":8.189,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":48,"adapter":"dartvex","operationCount":1000,"traceSha256":"34d1c2996c4711c53b654ccb9172bdd508ff7023bd0729c37305aff833a9e8c3","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[49.063,41.41,39.591,46.698,41.634,44.904,47.431,47.117,43.602,43.18,41.891,43.073,46.424,46.951,58.182,44.262,56.578,54.64,42.757,41.798],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"e7e8f860ed711b1ba4c898d3c6a3a7443cdcb343f3b965d64050f635bebb045a","reconnectToLiveMs":100.174,"canonicalVerifierHash":"882b979772b174022efc16c448d930c4c985077f663f74e281e64d2730ca2d9e","roundTripHash":"d800ff5ea7d263d57c07e1a252a14b4b8787f97831d2de8d8608dfa3a4187f23","remoteConvergenceMs":7.825,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10},{"seed":49,"adapter":"dartvex","operationCount":1000,"traceSha256":"cd10c09eb29b4c21d63fff22d70e92b231eb178d82ab60ea4f2dd0b671e6f0c5","faultScheduleSha256":"2916373f0d5b6c5ff58578c3a72a0bc436584dab36f1ce931403091816eb0c7e","faultSchedule":[{"fault":"offline_queue","beforeBatch":0},{"fault":"delay","beforeBatch":3,"milliseconds":25},{"fault":"duplicate","afterBatch":4},{"fault":"subscription_restart","beforeBatch":7},{"fault":"reconnect","beforeBatch":14}],"faults":{"offlineQueuedEdits":true,"delayedDelivery":true,"duplicatedDelivery":true,"subscriptionRestart":true,"reconnect":true,"deleteRecreate":true,"revisionConflict":true,"boundedRetries":true,"authRefresh":false,"processRestart":false},"acknowledged":910,"rejected":90,"unresolved":0,"retryCount":0,"batchLatencyMs":[57.603,50.432,57.113,49.102,55.483,48.389,46.91,47.169,48.298,45.564,45.195,49.735,49.842,63.798,73.241,50.371,59.511,60.673,43.486,36.835],"auth":{"exercised":false,"rejectedTokenObserved":false,"refreshSessionCalled":false,"tokenChanged":false,"reconnectCalled":false,"recoveryMs":null,"freshTokenAcceptedMs":null,"manualReconnectMs":null,"postReconnectAcceptedMs":null,"acceptedAfterRefresh":false,"queuedBatchReplayedExactlyOnce":false},"duplicateNoopHash":"8d2697434c6eed85928903a55f2524696d540ea4d20637d254aaa9ff692314fc","reconnectToLiveMs":136.579,"canonicalVerifierHash":"897c1e45d48565825398a418ad07d9557552005e11b69982d8979da0e6def54e","roundTripHash":"a93b4e6243f97268681c90698b8754de9e2cf2decf12fb6104cb376aa9ace707","remoteConvergenceMs":7.972,"finalStrategyRevision":15,"finalPageCount":2,"finalElementCount":82,"finalLineupCount":10}]} \ No newline at end of file diff --git a/tool/convex_client_gauntlet/runtime/results/fair_rerun_matrix.json b/tool/convex_client_gauntlet/runtime/results/fair_rerun_matrix.json new file mode 100644 index 00000000..58fa93bb --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/results/fair_rerun_matrix.json @@ -0,0 +1,111 @@ +{ + "schemaVersion": 2, + "evaluation": "convex_dart_client_fair_rerun", + "harnessCommit": "fb83488c0924f8daf57c4bdfc48d7a4a5ff0c8f5", + "deployment": "local:127.0.0.1:3210", + "baseFixture": { + "path": "test/fixtures/strategy_integrity/base-test-v43.ica", + "sha256": "8544873d608a0ad885b2e6042a383596a0b1dc37514034281b4e4eec6168756a" + }, + "tooling": { + "dartvex": { + "version": "0.2.0", + "strictGatePassed": true, + "explicitResultRenameCaught": true, + "missingReturnRejectedByIcarusWrapper": true, + "unsupportedValidatorRejectedByIcarusWrapper": true, + "deterministicGeneration": true, + "stableReturnSurfaceCompleteForRuntime": false + }, + "convex_flutter": { + "version": "3.0.1 + Icarus patch", + "convexRustVersion": "0.10.4 + Icarus patch", + "generatedContractBoundary": false, + "flutterRustBridgeVersion": "2.11.1 pinned", + "authCallbackOwnedByConvexStateMachine": true, + "realManualReconnect": true, + "vendoredPackageRequired": true, + "vendoredConvexRustRequired": true + } + }, + "correctness": { + "equivalentSeedZeroTrace": true, + "equivalentSeedZeroFaultSchedule": true, + "dartvex": { + "status": "passed", + "seedsCompleted": 50, + "operationsCompleted": 50000, + "acknowledged": 45500, + "visibleRevisionRejects": 4500, + "unresolved": 0, + "authRefreshAccepted": true, + "queuedBatchReplayedExactlyOnce": true, + "processRestartRecovered": true, + "allCanonicalVerifierHashesPassed": true, + "allIcaRoundTripsPassed": true, + "reportSha256": "df57f96fca7393df0097d70dfefcb58d6de6aeb375eba739f5ea3db69edbbd9a" + }, + "convex_flutter": { + "status": "passed", + "seedsCompleted": 50, + "operationsCompleted": 50000, + "acknowledged": 45500, + "visibleRevisionRejects": 4500, + "unresolved": 0, + "authRefreshAccepted": true, + "queuedBatchReplayedExactlyOnce": true, + "processRestartRecovered": true, + "allCanonicalVerifierHashesPassed": true, + "allIcaRoundTripsPassed": true, + "reportSha256": "72cae3ed8f244f4122de1394008587da89057e44d102c9c2968315dd951e7b3f" + } + }, + "profile": { + "status": "passed", + "buildMode": "Flutter macOS profile", + "pairedTrialsPerAdapter": 10, + "totalCandidateRuns": 20, + "orderAlternated": true, + "pairedProfileReportSha256": "b1242bbdb567751648acb3c17c2e05161eae0db2dae60488d144bcfdac8b06c2", + "dartvexMedian": { + "remoteConvergenceMs": 7.3035, + "reconnectToLiveMs": 110.8925, + "authFreshTokenAcceptedMs": 9.1435, + "authRecoveryMs": 147.8825, + "maxRssBytes": 134406144, + "averageProcessCpuPercent": 9.248290046558877, + "transferredBytes": 1439001, + "runnerWallClockMs": 2749.5435 + }, + "convexFlutterMedian": { + "remoteConvergenceMs": 7.459, + "reconnectToLiveMs": 73.2425, + "authFreshTokenAcceptedMs": 127.134, + "authRecoveryMs": 205.734, + "maxRssBytes": 141312000, + "averageProcessCpuPercent": 14.533420361788625, + "transferredBytes": 1652716, + "runnerWallClockMs": 1873.387 + }, + "tradeoff": { + "convexFlutterRunnerWallClockFasterPercent": 31.865526040959157, + "convexFlutterReconnectFasterPercent": 33.95180016682823, + "convexFlutterRssHigherPercent": 5.138050831961971, + "convexFlutterCpuHigherPercent": 57.147108153212066, + "convexFlutterTransferHigherPercent": 14.851622757732619 + }, + "desktopTargetsMeasured": ["macos"], + "desktopTargetsNotMeasuredOnThisHost": ["windows", "linux"] + }, + "verdict": { + "correctnessWinner": null, + "correctnessResult": "tie", + "performanceWinner": null, + "performanceResult": "convex_flutter is faster in wall time and reconnect; Dartvex uses less CPU, RSS, and transfer", + "adoptionDecision": "keep_convex_flutter", + "applicationClientChanged": false, + "applicationDependencyPatched": true, + "reason": "Both clients are correct. Migrating to Dartvex is not justified while its stable generated runtime return surface is incomplete; convex_flutter remains the current client but now carries a local package and Rust maintenance burden.", + "nextStep": "Upstream the convex_flutter and convex-rs fixes, and separately complete Dartvex's generated return surface before reconsidering a migration." + } +} diff --git a/tool/convex_client_gauntlet/runtime/results/paired_profile_macos.json b/tool/convex_client_gauntlet/runtime/results/paired_profile_macos.json new file mode 100644 index 00000000..e672672b --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/results/paired_profile_macos.json @@ -0,0 +1,838 @@ +{ + "schemaVersion": 1, + "status": "passed", + "gitCommit": "fb83488c0924f8daf57c4bdfc48d7a4a5ff0c8f5", + "deployment": "local:127.0.0.1:3210", + "trialCountPerAdapter": 10, + "pairing": "alternating first position by trial", + "profileMode": "Flutter macOS profile build", + "cpuDefinition": "(process user seconds + system seconds) / runner wall-clock seconds", + "transferDefinition": "application JSON bytes recorded by the neutral transport; excludes WebSocket framing and protocol metadata", + "percentileDefinition": "nearest-rank p95", + "machine": { + "operatingSystem": "macos", + "operatingSystemVersion": "Version 26.5.1 (Build 25F80)", + "processors": 8, + "dartVersion": "3.11.0 (stable) (Mon Feb 9 00:38:07 2026 -0800) on \"macos_arm64\"" + }, + "toolVersions": { + "flutter": "3.41.1 stable", + "dart": "3.11.0 macos_arm64", + "rustc": "1.90.0 stable", + "node": "23.11.0", + "convexCli": "1.45.0", + "dartvex": "0.2.0", + "convexFlutter": "3.0.1 + Icarus patch", + "convexRust": "0.10.4 + Icarus patch", + "flutterRustBridge": "2.11.1 pinned" + }, + "desktopTargets": { + "packageSupported": [ + "macos", + "windows", + "linux" + ], + "measured": [ + "macos (universal arm64 + x86_64 build, arm64 host run)" + ], + "notMeasured": [ + "windows (requires Windows host)", + "linux (requires Linux host)" + ] + }, + "buildSize": { + "sharedHarnessBundleBytes": 78540800, + "convexFlutterNativeFrameworkExecutableBytes": 23195280, + "sharedAppFrameworkExecutableBytes": 8000528, + "candidateIsolatedBundleBytes": null, + "note": "The same harness bundle contains both adapters; only the native convex_flutter framework is candidate-specific." + }, + "summary": { + "dartvex": { + "remoteConvergenceMs": { + "median": 7.3035, + "p95": 7.804, + "min": 7.138, + "max": 7.804, + "values": [ + 7.318, + 7.39, + 7.451, + 7.804, + 7.369, + 7.242, + 7.289, + 7.264, + 7.246, + 7.138 + ] + }, + "reconnectToLiveMs": { + "median": 110.8925, + "p95": 150.191, + "min": 60.14, + "max": 150.191, + "values": [ + 128.669, + 71.132, + 69.177, + 150.191, + 60.14, + 98.294, + 111.336, + 110.449, + 140.548, + 142.599 + ] + }, + "authFreshTokenAcceptedMs": { + "median": 9.1435, + "p95": 9.662, + "min": 2.478, + "max": 9.662, + "values": [ + 8.819, + 8.411, + 8.754, + 2.478, + 9.241, + 9.662, + 9.36, + 9.166, + 9.121, + 9.493 + ] + }, + "authRecoveryMs": { + "median": 147.8825, + "p95": 166.973, + "min": 67.838, + "max": 166.973, + "values": [ + 138.399, + 153.409, + 153.956, + 111.622, + 156.27, + 67.838, + 154.531, + 93.486, + 142.356, + 166.973 + ] + }, + "maxRssBytes": { + "median": 134406144.0, + "p95": 134856704.0, + "min": 134201344.0, + "max": 134856704.0, + "values": [ + 134856704.0, + 134250496.0, + 134774784.0, + 134758400.0, + 134332416.0, + 134381568.0, + 134250496.0, + 134594560.0, + 134430720.0, + 134201344.0 + ] + }, + "averageProcessCpuPercent": { + "median": 9.248290046558877, + "p95": 10.118065684863536, + "min": 7.361192206911925, + "max": 10.118065684863536, + "values": [ + 8.009492732126965, + 10.118065684863536, + 9.30226673914993, + 7.361192206911925, + 9.352467522657253, + 9.598999861006481, + 9.661951505505959, + 8.087238660882674, + 7.822167239740701, + 9.194313353967823 + ] + }, + "bytesSent": { + "median": 420054.0, + "p95": 420054.0, + "min": 420054.0, + "max": 420054.0, + "values": [ + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0 + ] + }, + "bytesReceived": { + "median": 1018947.0, + "p95": 1018947.0, + "min": 1018947.0, + "max": 1018947.0, + "values": [ + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0, + 1018947.0 + ] + }, + "transferredBytes": { + "median": 1439001.0, + "p95": 1439001.0, + "min": 1439001.0, + "max": 1439001.0, + "values": [ + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0, + 1439001.0 + ] + }, + "runnerWallClockMs": { + "median": 2749.5434999999998, + "p95": 3396.189, + "min": 2470.828, + "max": 3396.189, + "values": [ + 3371.0, + 2470.828, + 2687.517, + 3396.189, + 2780.015, + 2604.438, + 2587.469, + 3091.29, + 3323.887, + 2719.072 + ] + } + }, + "convex_flutter": { + "remoteConvergenceMs": { + "median": 7.459, + "p95": 7.575, + "min": 7.294, + "max": 7.575, + "values": [ + 7.471, + 7.348, + 7.457, + 7.461, + 7.405, + 7.294, + 7.473, + 7.575, + 7.509, + 7.436 + ] + }, + "reconnectToLiveMs": { + "median": 73.2425, + "p95": 94.765, + "min": 20.316, + "max": 94.765, + "values": [ + 20.316, + 87.859, + 91.76, + 77.776, + 94.765, + 68.709, + 49.212, + 26.962, + 38.532, + 93.847 + ] + }, + "authFreshTokenAcceptedMs": { + "median": 127.134, + "p95": 171.027, + "min": 73.848, + "max": 171.027, + "values": [ + 107.752, + 73.848, + 156.794, + 170.649, + 104.023, + 122.243, + 132.025, + 89.714, + 171.027, + 150.843 + ] + }, + "authRecoveryMs": { + "median": 205.73399999999998, + "p95": 543.221, + "min": 110.845, + "max": 543.221, + "values": [ + 200.155, + 114.574, + 175.913, + 211.313, + 117.57, + 543.221, + 304.983, + 110.845, + 529.07, + 359.654 + ] + }, + "maxRssBytes": { + "median": 141312000.0, + "p95": 141541376.0, + "min": 140984320.0, + "max": 141541376.0, + "values": [ + 141393920.0, + 141328384.0, + 141279232.0, + 141295616.0, + 140984320.0, + 141279232.0, + 141541376.0, + 141197312.0, + 141475840.0, + 141492224.0 + ] + }, + "averageProcessCpuPercent": { + "median": 14.533420361788625, + "p95": 15.306079064401745, + "min": 11.674009762931128, + "max": 15.306079064401745, + "values": [ + 15.266917516661389, + 11.674009762931128, + 14.381183217380435, + 15.031850821685506, + 15.306079064401745, + 13.104043767506186, + 14.638662904530303, + 15.261467084830866, + 14.266689690152097, + 14.42817781904695 + ] + }, + "bytesSent": { + "median": 420054.0, + "p95": 420054.0, + "min": 420054.0, + "max": 420054.0, + "values": [ + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0, + 420054.0 + ] + }, + "bytesReceived": { + "median": 1232662.0, + "p95": 1351447.0, + "min": 1199927.0, + "max": 1351447.0, + "values": [ + 1199927.0, + 1351447.0, + 1228252.0, + 1265397.0, + 1228252.0, + 1237072.0, + 1199927.0, + 1228252.0, + 1277157.0, + 1237072.0 + ] + }, + "transferredBytes": { + "median": 1652716.0, + "p95": 1771501.0, + "min": 1619981.0, + "max": 1771501.0, + "values": [ + 1619981.0, + 1771501.0, + 1648306.0, + 1685451.0, + 1648306.0, + 1657126.0, + 1619981.0, + 1648306.0, + 1697211.0, + 1657126.0 + ] + }, + "runnerWallClockMs": { + "median": 1873.387, + "p95": 2312.83, + "min": 1703.637, + "max": 2312.83, + "values": [ + 1834.031, + 2312.83, + 1807.918, + 1796.186, + 1764.005, + 2136.745, + 1912.743, + 1703.637, + 2032.707, + 1940.647 + ] + } + } + }, + "samples": [ + { + "trial": 1, + "position": "first", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-01-first-dartvex.json", + "timeFile": "profile-trial-01-first-dartvex.time", + "remoteConvergenceMs": 7.318, + "reconnectToLiveMs": 128.669, + "authFreshTokenAcceptedMs": 8.819, + "authRecoveryMs": 138.399, + "maxRssBytes": 134856704, + "averageProcessCpuPercent": 8.009492732126965, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 3371.0, + "processRealSeconds": 4.34, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.07 + }, + { + "trial": 1, + "position": "second", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-01-second-convex_flutter.json", + "timeFile": "profile-trial-01-second-convex_flutter.time", + "remoteConvergenceMs": 7.471, + "reconnectToLiveMs": 20.316, + "authFreshTokenAcceptedMs": 107.752, + "authRecoveryMs": 200.155, + "maxRssBytes": 141393920, + "averageProcessCpuPercent": 15.266917516661389, + "bytesSent": 420054, + "bytesReceived": 1199927, + "transferredBytes": 1619981, + "runnerWallClockMs": 1834.031, + "processRealSeconds": 1.98, + "processUserSeconds": 0.22, + "processSystemSeconds": 0.06 + }, + { + "trial": 2, + "position": "first", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-02-first-convex_flutter.json", + "timeFile": "profile-trial-02-first-convex_flutter.time", + "remoteConvergenceMs": 7.348, + "reconnectToLiveMs": 87.859, + "authFreshTokenAcceptedMs": 73.848, + "authRecoveryMs": 114.574, + "maxRssBytes": 141328384, + "averageProcessCpuPercent": 11.674009762931128, + "bytesSent": 420054, + "bytesReceived": 1351447, + "transferredBytes": 1771501, + "runnerWallClockMs": 2312.83, + "processRealSeconds": 2.46, + "processUserSeconds": 0.22, + "processSystemSeconds": 0.05 + }, + { + "trial": 2, + "position": "second", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-02-second-dartvex.json", + "timeFile": "profile-trial-02-second-dartvex.time", + "remoteConvergenceMs": 7.39, + "reconnectToLiveMs": 71.132, + "authFreshTokenAcceptedMs": 8.411, + "authRecoveryMs": 153.409, + "maxRssBytes": 134250496, + "averageProcessCpuPercent": 10.118065684863536, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2470.828, + "processRealSeconds": 2.61, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 3, + "position": "first", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-03-first-dartvex.json", + "timeFile": "profile-trial-03-first-dartvex.time", + "remoteConvergenceMs": 7.451, + "reconnectToLiveMs": 69.177, + "authFreshTokenAcceptedMs": 8.754, + "authRecoveryMs": 153.956, + "maxRssBytes": 134774784, + "averageProcessCpuPercent": 9.30226673914993, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2687.517, + "processRealSeconds": 2.83, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 3, + "position": "second", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-03-second-convex_flutter.json", + "timeFile": "profile-trial-03-second-convex_flutter.time", + "remoteConvergenceMs": 7.457, + "reconnectToLiveMs": 91.76, + "authFreshTokenAcceptedMs": 156.794, + "authRecoveryMs": 175.913, + "maxRssBytes": 141279232, + "averageProcessCpuPercent": 14.381183217380435, + "bytesSent": 420054, + "bytesReceived": 1228252, + "transferredBytes": 1648306, + "runnerWallClockMs": 1807.918, + "processRealSeconds": 1.95, + "processUserSeconds": 0.21, + "processSystemSeconds": 0.05 + }, + { + "trial": 4, + "position": "first", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-04-first-convex_flutter.json", + "timeFile": "profile-trial-04-first-convex_flutter.time", + "remoteConvergenceMs": 7.461, + "reconnectToLiveMs": 77.776, + "authFreshTokenAcceptedMs": 170.649, + "authRecoveryMs": 211.313, + "maxRssBytes": 141295616, + "averageProcessCpuPercent": 15.031850821685506, + "bytesSent": 420054, + "bytesReceived": 1265397, + "transferredBytes": 1685451, + "runnerWallClockMs": 1796.186, + "processRealSeconds": 1.94, + "processUserSeconds": 0.22, + "processSystemSeconds": 0.05 + }, + { + "trial": 4, + "position": "second", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-04-second-dartvex.json", + "timeFile": "profile-trial-04-second-dartvex.time", + "remoteConvergenceMs": 7.804, + "reconnectToLiveMs": 150.191, + "authFreshTokenAcceptedMs": 2.478, + "authRecoveryMs": 111.622, + "maxRssBytes": 134758400, + "averageProcessCpuPercent": 7.361192206911925, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 3396.189, + "processRealSeconds": 3.54, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 5, + "position": "first", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-05-first-dartvex.json", + "timeFile": "profile-trial-05-first-dartvex.time", + "remoteConvergenceMs": 7.369, + "reconnectToLiveMs": 60.14, + "authFreshTokenAcceptedMs": 9.241, + "authRecoveryMs": 156.27, + "maxRssBytes": 134332416, + "averageProcessCpuPercent": 9.352467522657253, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2780.015, + "processRealSeconds": 2.93, + "processUserSeconds": 0.21, + "processSystemSeconds": 0.05 + }, + { + "trial": 5, + "position": "second", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-05-second-convex_flutter.json", + "timeFile": "profile-trial-05-second-convex_flutter.time", + "remoteConvergenceMs": 7.405, + "reconnectToLiveMs": 94.765, + "authFreshTokenAcceptedMs": 104.023, + "authRecoveryMs": 117.57, + "maxRssBytes": 140984320, + "averageProcessCpuPercent": 15.306079064401745, + "bytesSent": 420054, + "bytesReceived": 1228252, + "transferredBytes": 1648306, + "runnerWallClockMs": 1764.005, + "processRealSeconds": 1.91, + "processUserSeconds": 0.22, + "processSystemSeconds": 0.05 + }, + { + "trial": 6, + "position": "first", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-06-first-convex_flutter.json", + "timeFile": "profile-trial-06-first-convex_flutter.time", + "remoteConvergenceMs": 7.294, + "reconnectToLiveMs": 68.709, + "authFreshTokenAcceptedMs": 122.243, + "authRecoveryMs": 543.221, + "maxRssBytes": 141279232, + "averageProcessCpuPercent": 13.104043767506186, + "bytesSent": 420054, + "bytesReceived": 1237072, + "transferredBytes": 1657126, + "runnerWallClockMs": 2136.745, + "processRealSeconds": 2.28, + "processUserSeconds": 0.23, + "processSystemSeconds": 0.05 + }, + { + "trial": 6, + "position": "second", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-06-second-dartvex.json", + "timeFile": "profile-trial-06-second-dartvex.time", + "remoteConvergenceMs": 7.242, + "reconnectToLiveMs": 98.294, + "authFreshTokenAcceptedMs": 9.662, + "authRecoveryMs": 67.838, + "maxRssBytes": 134381568, + "averageProcessCpuPercent": 9.598999861006481, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2604.438, + "processRealSeconds": 2.75, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 7, + "position": "first", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-07-first-dartvex.json", + "timeFile": "profile-trial-07-first-dartvex.time", + "remoteConvergenceMs": 7.289, + "reconnectToLiveMs": 111.336, + "authFreshTokenAcceptedMs": 9.36, + "authRecoveryMs": 154.531, + "maxRssBytes": 134250496, + "averageProcessCpuPercent": 9.661951505505959, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2587.469, + "processRealSeconds": 2.73, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 7, + "position": "second", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-07-second-convex_flutter.json", + "timeFile": "profile-trial-07-second-convex_flutter.time", + "remoteConvergenceMs": 7.473, + "reconnectToLiveMs": 49.212, + "authFreshTokenAcceptedMs": 132.025, + "authRecoveryMs": 304.983, + "maxRssBytes": 141541376, + "averageProcessCpuPercent": 14.638662904530303, + "bytesSent": 420054, + "bytesReceived": 1199927, + "transferredBytes": 1619981, + "runnerWallClockMs": 1912.743, + "processRealSeconds": 2.05, + "processUserSeconds": 0.23, + "processSystemSeconds": 0.05 + }, + { + "trial": 8, + "position": "first", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-08-first-convex_flutter.json", + "timeFile": "profile-trial-08-first-convex_flutter.time", + "remoteConvergenceMs": 7.575, + "reconnectToLiveMs": 26.962, + "authFreshTokenAcceptedMs": 89.714, + "authRecoveryMs": 110.845, + "maxRssBytes": 141197312, + "averageProcessCpuPercent": 15.261467084830866, + "bytesSent": 420054, + "bytesReceived": 1228252, + "transferredBytes": 1648306, + "runnerWallClockMs": 1703.637, + "processRealSeconds": 1.85, + "processUserSeconds": 0.21, + "processSystemSeconds": 0.05 + }, + { + "trial": 8, + "position": "second", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-08-second-dartvex.json", + "timeFile": "profile-trial-08-second-dartvex.time", + "remoteConvergenceMs": 7.264, + "reconnectToLiveMs": 110.449, + "authFreshTokenAcceptedMs": 9.166, + "authRecoveryMs": 93.486, + "maxRssBytes": 134594560, + "averageProcessCpuPercent": 8.087238660882674, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 3091.29, + "processRealSeconds": 3.24, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + }, + { + "trial": 9, + "position": "first", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-09-first-dartvex.json", + "timeFile": "profile-trial-09-first-dartvex.time", + "remoteConvergenceMs": 7.246, + "reconnectToLiveMs": 140.548, + "authFreshTokenAcceptedMs": 9.121, + "authRecoveryMs": 142.356, + "maxRssBytes": 134430720, + "averageProcessCpuPercent": 7.822167239740701, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 3323.887, + "processRealSeconds": 3.47, + "processUserSeconds": 0.21, + "processSystemSeconds": 0.05 + }, + { + "trial": 9, + "position": "second", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-09-second-convex_flutter.json", + "timeFile": "profile-trial-09-second-convex_flutter.time", + "remoteConvergenceMs": 7.509, + "reconnectToLiveMs": 38.532, + "authFreshTokenAcceptedMs": 171.027, + "authRecoveryMs": 529.07, + "maxRssBytes": 141475840, + "averageProcessCpuPercent": 14.266689690152097, + "bytesSent": 420054, + "bytesReceived": 1277157, + "transferredBytes": 1697211, + "runnerWallClockMs": 2032.707, + "processRealSeconds": 2.17, + "processUserSeconds": 0.23, + "processSystemSeconds": 0.06 + }, + { + "trial": 10, + "position": "first", + "adapter": "convex_flutter", + "status": "passed", + "reportFile": "profile-trial-10-first-convex_flutter.json", + "timeFile": "profile-trial-10-first-convex_flutter.time", + "remoteConvergenceMs": 7.436, + "reconnectToLiveMs": 93.847, + "authFreshTokenAcceptedMs": 150.843, + "authRecoveryMs": 359.654, + "maxRssBytes": 141492224, + "averageProcessCpuPercent": 14.42817781904695, + "bytesSent": 420054, + "bytesReceived": 1237072, + "transferredBytes": 1657126, + "runnerWallClockMs": 1940.647, + "processRealSeconds": 2.08, + "processUserSeconds": 0.23, + "processSystemSeconds": 0.05 + }, + { + "trial": 10, + "position": "second", + "adapter": "dartvex", + "status": "passed", + "reportFile": "profile-trial-10-second-dartvex.json", + "timeFile": "profile-trial-10-second-dartvex.time", + "remoteConvergenceMs": 7.138, + "reconnectToLiveMs": 142.599, + "authFreshTokenAcceptedMs": 9.493, + "authRecoveryMs": 166.973, + "maxRssBytes": 134201344, + "averageProcessCpuPercent": 9.194313353967823, + "bytesSent": 420054, + "bytesReceived": 1018947, + "transferredBytes": 1439001, + "runnerWallClockMs": 2719.072, + "processRealSeconds": 2.86, + "processUserSeconds": 0.2, + "processSystemSeconds": 0.05 + } + ] +} diff --git a/tool/convex_client_gauntlet/runtime/test/transport_smoke_test.dart b/tool/convex_client_gauntlet/runtime/test/transport_smoke_test.dart new file mode 100644 index 00000000..23ce8728 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/test/transport_smoke_test.dart @@ -0,0 +1,27 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus_convex_runtime_gauntlet/transport.dart'; + +const deploymentUrl = String.fromEnvironment('CONVEX_URL'); + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + test( + 'both transports reach the same deployment', + () async { + final dartvex = DartvexTransport(deploymentUrl); + final dartvexResult = await dartvex.query('users:me', const {}); + expect(dartvexResult, isNull); + await dartvex.close(); + + final convexFlutter = await ConvexFlutterTransport.create(deploymentUrl); + final convexFlutterResult = await convexFlutter.query( + 'users:me', + const {}, + ); + expect(convexFlutterResult, isNull); + await convexFlutter.close(); + }, + skip: deploymentUrl.isEmpty ? 'Pass --dart-define=CONVEX_URL' : false, + ); +} diff --git a/tool/convex_client_gauntlet/runtime/test/workload_test.dart b/tool/convex_client_gauntlet/runtime/test/workload_test.dart new file mode 100644 index 00000000..ddef635f --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/test/workload_test.dart @@ -0,0 +1,74 @@ +import 'dart:convert'; + +import 'package:flutter_test/flutter_test.dart'; +import 'package:icarus_convex_runtime_gauntlet/runner.dart'; +import 'package:icarus_convex_runtime_gauntlet/workload.dart'; + +void main() { + test('each seed has one deterministic 1,000-op trace', () { + for (var seed = 0; seed < 50; seed += 1) { + final first = buildOperationTrace(seed); + final second = buildOperationTrace(seed); + expect(first, hasLength(operationsPerSeed)); + expect(canonicalHash(first), canonicalHash(second)); + expect( + first.map((operation) => operation['opId']).toSet(), + hasLength(operationsPerSeed), + ); + for (final operation in first) { + expect(operation['type'], isA()); + expect(operation, isNot(contains('kind'))); + expect(operation, isNot(contains('entityType'))); + expect(operation, isNot(contains('entityPublicId'))); + expect(operation, isNot(contains('expectedRevision'))); + } + } + }); + + test('the trace covers every synced entity boundary', () { + final trace = buildOperationTrace(0); + final types = trace.map((operation) => operation['type'] as String); + expect( + types.map((type) => type.split('.').first).toSet(), + containsAll({ + 'strategy', + 'page', + 'pageContent', + 'element', + 'lineup', + }), + ); + expect(types.where((type) => type.endsWith('.delete')), isNotEmpty); + expect(types.where((type) => type.endsWith('.reorder')), isNotEmpty); + }); + + test('canonical snapshots exclude transport clocks', () { + Object? state(double createdAt, double updatedAt) => canonicalSnapshot( + seed: 0, + snapshot: { + 'header': {'publicId': 'seed-0-strategy', 'createdAt': createdAt}, + 'pages': [ + { + 'publicId': 'seed-0-page', + 'contentCreatedAt': createdAt, + 'contentUpdatedAt': updatedAt, + }, + ], + 'elements': [], + 'lineups': [], + }, + folders: [], + ); + + expect(canonicalHash(state(1, 2)), canonicalHash(state(10, 20))); + }); + + test('rejected auth fixture is an expired, decodable JWT', () { + final parts = rejectedExpiredAccessToken.split('.'); + expect(parts, hasLength(3)); + final claims = + jsonDecode(utf8.decode(base64Url.decode(base64Url.normalize(parts[1])))) + as Map; + expect(claims['exp'], 0); + }); +} diff --git a/tool/convex_client_gauntlet/runtime/tool/export_canonical_state.dart b/tool/convex_client_gauntlet/runtime/tool/export_canonical_state.dart new file mode 100644 index 00000000..27ada380 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/tool/export_canonical_state.dart @@ -0,0 +1,48 @@ +import 'dart:io'; + +import 'package:dartvex/dartvex.dart'; +import 'package:supabase/supabase.dart'; + +import 'package:icarus_convex_runtime_gauntlet/workload.dart'; + +Future main(List arguments) async { + if (arguments.length != 1) { + stderr.writeln('Usage: dart run tool/export_canonical_state.dart '); + exitCode = 64; + return; + } + final environment = Platform.environment; + final supabase = SupabaseClient( + environment['SUPABASE_URL']!, + environment['SUPABASE_KEY']!, + authOptions: const AuthClientOptions( + authFlowType: AuthFlowType.implicit, + autoRefreshToken: false, + ), + ); + final auth = await supabase.auth.signInWithPassword( + email: environment['TEST_EMAIL']!, + password: environment['TEST_PASSWORD']!, + ); + final session = auth.session ?? (throw StateError('Sign-in failed')); + final convex = ConvexClient(environment['CONVEX_URL']!); + await convex.setAuth(session.accessToken); + try { + final snapshot = await convex.query('strategy:getFullSnapshot', { + 'strategyPublicId': strategyId(0), + }); + final folders = await convex.query('folders:listTree', {'scope': 'all'}); + final canonical = canonicalSnapshot( + seed: 0, + snapshot: snapshot, + folders: folders, + ); + await File( + arguments.single, + ).writeAsString(canonicalJson(canonical), flush: true); + stdout.writeln(canonicalHash(canonical)); + } finally { + await convex.close(); + await supabase.dispose(); + } +} diff --git a/tool/convex_client_gauntlet/runtime/tool/provision_test_account.dart b/tool/convex_client_gauntlet/runtime/tool/provision_test_account.dart new file mode 100644 index 00000000..8ba5dae0 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/tool/provision_test_account.dart @@ -0,0 +1,153 @@ +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:http/http.dart' as http; +import 'package:supabase/supabase.dart'; + +const supabaseUrl = String.fromEnvironment('SUPABASE_URL'); +const supabaseKey = String.fromEnvironment('SUPABASE_KEY'); + +Future main(List arguments) async { + if (supabaseUrl.isEmpty || supabaseKey.isEmpty || arguments.length != 1) { + stderr.writeln( + 'Usage: dart --define=SUPABASE_URL=... --define=SUPABASE_KEY=... ' + 'run tool/provision_test_account.dart ', + ); + exitCode = 64; + return; + } + + final output = File(arguments.single); + final random = Random.secure(); + final mailboxPassword = _randomSecret(random, 32); + final accountPassword = '${_randomSecret(random, 24)}aA7!'; + final mailClient = http.Client(); + final supabase = SupabaseClient( + supabaseUrl, + supabaseKey, + authOptions: const AuthClientOptions(authFlowType: AuthFlowType.implicit), + ); + + try { + final domainResponse = await mailClient.get( + Uri.parse('https://api.mail.tm/domains?page=1'), + ); + _requireSuccess(domainResponse, 'list disposable mailbox domains'); + final domainBody = jsonDecode(domainResponse.body) as Map; + final domains = domainBody['hydra:member'] as List; + final domain = (domains.first as Map)['domain'] as String; + final address = + 'icarus-gauntlet-${DateTime.now().microsecondsSinceEpoch}@$domain'; + + final accountResponse = await mailClient.post( + Uri.parse('https://api.mail.tm/accounts'), + headers: const {'content-type': 'application/json'}, + body: jsonEncode({'address': address, 'password': mailboxPassword}), + ); + _requireSuccess(accountResponse, 'create disposable mailbox'); + + final tokenResponse = await mailClient.post( + Uri.parse('https://api.mail.tm/token'), + headers: const {'content-type': 'application/json'}, + body: jsonEncode({'address': address, 'password': mailboxPassword}), + ); + _requireSuccess(tokenResponse, 'authenticate disposable mailbox'); + final mailToken = + (jsonDecode(tokenResponse.body) as Map)['token'] + as String; + + await supabase.auth.signUp( + email: address, + password: accountPassword, + data: const {'display_name': 'Icarus Convex gauntlet'}, + ); + + final confirmationUrl = await _waitForConfirmation( + client: mailClient, + token: mailToken, + ); + final confirmationRequest = http.Request('GET', confirmationUrl) + ..followRedirects = false; + final confirmationResponse = await mailClient.send(confirmationRequest); + if (confirmationResponse.statusCode >= 400) { + throw StateError( + 'Supabase confirmation failed with HTTP ' + '${confirmationResponse.statusCode}', + ); + } + + final auth = await supabase.auth.signInWithPassword( + email: address, + password: accountPassword, + ); + if (auth.session == null) { + throw StateError('Confirmed test account did not produce a session'); + } + + await output.writeAsString( + jsonEncode({'email': address, 'password': accountPassword}), + flush: true, + ); + await Process.run('chmod', ['600', output.path]); + stdout.writeln('Disposable Supabase test account is confirmed and ready.'); + } finally { + mailClient.close(); + await supabase.dispose(); + } +} + +Future _waitForConfirmation({ + required http.Client client, + required String token, +}) async { + final deadline = DateTime.now().add(const Duration(minutes: 2)); + final headers = {'authorization': 'Bearer $token'}; + while (DateTime.now().isBefore(deadline)) { + final messagesResponse = await client.get( + Uri.parse('https://api.mail.tm/messages?page=1'), + headers: headers, + ); + _requireSuccess(messagesResponse, 'poll disposable mailbox'); + final body = jsonDecode(messagesResponse.body) as Map; + final messages = body['hydra:member'] as List; + if (messages.isNotEmpty) { + final id = (messages.first as Map)['id'] as String; + final messageResponse = await client.get( + Uri.parse('https://api.mail.tm/messages/$id'), + headers: headers, + ); + _requireSuccess(messageResponse, 'read confirmation message'); + final message = jsonDecode(messageResponse.body) as Map; + final source = [ + if (message['text'] is String) message['text'] as String, + if (message['html'] is List) + ...(message['html'] as List).whereType(), + ].join('\n'); + final match = RegExp( + r'''https://[^\s"'<>()\]]+/auth/v1/verify\?[^\s"'<>()\]]+''', + ).firstMatch(source); + if (match != null) { + return Uri.parse(match.group(0)!.replaceAll('&', '&')); + } + } + await Future.delayed(const Duration(seconds: 2)); + } + throw TimeoutException('Supabase confirmation email did not arrive'); +} + +String _randomSecret(Random random, int length) { + const alphabet = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789'; + return List.generate( + length, + (_) => alphabet[random.nextInt(alphabet.length)], + growable: false, + ).join(); +} + +void _requireSuccess(http.Response response, String operation) { + if (response.statusCode < 200 || response.statusCode >= 300) { + throw StateError('$operation failed with HTTP ${response.statusCode}'); + } +} diff --git a/tool/convex_client_gauntlet/runtime/tool/run_paired_profile.sh b/tool/convex_client_gauntlet/runtime/tool/run_paired_profile.sh new file mode 100755 index 00000000..50a27d03 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/tool/run_paired_profile.sh @@ -0,0 +1,85 @@ +#!/bin/sh + +set -eu + +: "${PROFILE_OUTPUT_DIR:?Set PROFILE_OUTPUT_DIR to an empty output directory}" +: "${SUPABASE_URL:?Set SUPABASE_URL}" +: "${SUPABASE_KEY:?Set SUPABASE_KEY to the public anon key}" +: "${TEST_EMAIL:?Set TEST_EMAIL to the disposable account}" +: "${TEST_PASSWORD:?Set TEST_PASSWORD to the disposable account}" +: "${CONVEX_SELF_HOSTED_ADMIN_KEY:?Set CONVEX_SELF_HOSTED_ADMIN_KEY for the isolated deployment}" + +profile_tool_dir=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd) +runtime_dir=$(CDPATH= cd -- "$profile_tool_dir/.." && pwd) +repo_dir=$(CDPATH= cd -- "$runtime_dir/../../.." && pwd) +app_dir="$runtime_dir/app" +profile_binary="$app_dir/build/macos/Build/Products/Profile/icarus_convex_runtime_runner.app/Contents/MacOS/icarus_convex_runtime_runner" +trial_count=${TRIALS:-10} +convex_url=${CONVEX_URL:-http://127.0.0.1:3210} + +if [ ! -x "$profile_binary" ]; then + printf 'Missing profile runner: %s\n' "$profile_binary" >&2 + exit 2 +fi + +mkdir -p "$PROFILE_OUTPUT_DIR" +printf 'trial\tposition\tadapter\treport\ttime\n' > "$PROFILE_OUTPUT_DIR/order.tsv" + +run_candidate() { + trial=$1 + position=$2 + adapter=$3 + report_name="profile-trial-$(printf '%02d' "$trial")-$position-$adapter" + log_file="$PROFILE_OUTPUT_DIR/$report_name.log" + time_file="$PROFILE_OUTPUT_DIR/$report_name.time" + + ( + cd "$repo_dir" + CONVEX_DEPLOYMENT= \ + CONVEX_SELF_HOSTED_URL="$convex_url" \ + npx convex import --replace-all --table users \ + "$runtime_dir/fixtures/empty.json" -y >/dev/null 2>&1 + ) + + if ! ( + cd "$app_dir" + export CONVEX_URL="$convex_url" + export ADAPTER="$adapter" + export SEED_COUNT=1 + export ALLOW_CHECKPOINT=0 + export RESET_PROGRESS=1 + export REPORT_NAME="$report_name" + export GIT_COMMIT + GIT_COMMIT=$(git -C "$repo_dir" rev-parse HEAD) + /usr/bin/time -lp "$profile_binary" > "$log_file" + ) 2> "$time_file"; then + tail -40 "$log_file" >&2 + tail -40 "$time_file" >&2 + exit 1 + fi + + report_path=$(sed -n 's/^GAUNTLET_RESULT://p' "$log_file" | tail -1 | jq -r .reportPath) + if [ ! -f "$report_path" ]; then + printf 'Missing report for trial %s, adapter %s\n' "$trial" "$adapter" >&2 + exit 1 + fi + cp "$report_path" "$PROFILE_OUTPUT_DIR/$report_name.json" + printf '%s\t%s\t%s\t%s\t%s\n' \ + "$trial" "$position" "$adapter" "$report_name.json" "$report_name.time" \ + >> "$PROFILE_OUTPUT_DIR/order.tsv" + printf 'trial=%s position=%s adapter=%s passed\n' "$trial" "$position" "$adapter" +} + +trial=1 +while [ "$trial" -le "$trial_count" ]; do + if [ $((trial % 2)) -eq 1 ]; then + first=dartvex + second=convex_flutter + else + first=convex_flutter + second=dartvex + fi + run_candidate "$trial" first "$first" + run_candidate "$trial" second "$second" + trial=$((trial + 1)) +done diff --git a/tool/convex_client_gauntlet/runtime/tool/summarize_paired_profile.dart b/tool/convex_client_gauntlet/runtime/tool/summarize_paired_profile.dart new file mode 100644 index 00000000..f1732c81 --- /dev/null +++ b/tool/convex_client_gauntlet/runtime/tool/summarize_paired_profile.dart @@ -0,0 +1,210 @@ +import 'dart:convert'; +import 'dart:io'; +import 'dart:math' as math; + +void main(List args) { + if (args.length != 4) { + stderr.writeln( + 'Usage: dart run tool/summarize_paired_profile.dart ' + ' ' + '', + ); + exitCode = 64; + return; + } + + final rawDirectory = Directory(args[0]); + final order = File( + '${rawDirectory.path}/order.tsv', + ).readAsLinesSync().skip(1).where((line) => line.trim().isNotEmpty); + final samples = >[]; + + for (final line in order) { + final [trialText, position, adapter, reportName, timeName] = line.split( + '\t', + ); + final report = + jsonDecode(File('${rawDirectory.path}/$reportName').readAsStringSync()) + as Map; + final seed = + (report['seeds'] as List).single as Map; + final auth = seed['auth'] as Map; + final timing = _parseTime( + File('${rawDirectory.path}/$timeName').readAsStringSync(), + ); + final runnerSeconds = (report['wallClockMs'] as num).toDouble() / 1000; + final cpuPercent = + 100 * (timing.userSeconds + timing.systemSeconds) / runnerSeconds; + + samples.add({ + 'trial': int.parse(trialText), + 'position': position, + 'adapter': adapter, + 'status': report['status'], + 'reportFile': reportName, + 'timeFile': timeName, + 'remoteConvergenceMs': seed['remoteConvergenceMs'], + 'reconnectToLiveMs': seed['reconnectToLiveMs'], + 'authFreshTokenAcceptedMs': auth['freshTokenAcceptedMs'], + 'authRecoveryMs': auth['recoveryMs'], + 'maxRssBytes': timing.maxRssBytes, + 'averageProcessCpuPercent': cpuPercent, + 'bytesSent': report['bytesSent'], + 'bytesReceived': report['bytesReceived'], + 'transferredBytes': + (report['bytesSent'] as int) + (report['bytesReceived'] as int), + 'runnerWallClockMs': report['wallClockMs'], + 'processRealSeconds': timing.realSeconds, + 'processUserSeconds': timing.userSeconds, + 'processSystemSeconds': timing.systemSeconds, + }); + } + + final commits = samples + .map( + (sample) => + jsonDecode( + File( + '${rawDirectory.path}/${sample['reportFile']}', + ).readAsStringSync(), + ) + as Map, + ) + .map((report) => report['gitCommit']) + .toSet(); + final firstReport = + jsonDecode( + File( + '${rawDirectory.path}/${samples.first['reportFile']}', + ).readAsStringSync(), + ) + as Map; + + final output = { + 'schemaVersion': 1, + 'status': samples.every((sample) => sample['status'] == 'passed') + ? 'passed' + : 'failed', + 'gitCommit': commits.length == 1 ? commits.single : commits.toList(), + 'deployment': firstReport['deployment'], + 'trialCountPerAdapter': samples.length ~/ 2, + 'pairing': 'alternating first position by trial', + 'profileMode': 'Flutter macOS profile build', + 'cpuDefinition': + '(process user seconds + system seconds) / runner wall-clock seconds', + 'transferDefinition': + 'application JSON bytes recorded by the neutral transport; excludes ' + 'WebSocket framing and protocol metadata', + 'percentileDefinition': 'nearest-rank p95', + 'machine': firstReport['machine'], + 'toolVersions': { + 'flutter': '3.41.1 stable', + 'dart': '3.11.0 macos_arm64', + 'rustc': '1.90.0 stable', + 'node': '23.11.0', + 'convexCli': '1.45.0', + 'dartvex': '0.2.0', + 'convexFlutter': '3.0.1 + Icarus patch', + 'convexRust': '0.10.4 + Icarus patch', + 'flutterRustBridge': '2.11.1 pinned', + }, + 'desktopTargets': { + 'packageSupported': ['macos', 'windows', 'linux'], + 'measured': ['macos (universal arm64 + x86_64 build, arm64 host run)'], + 'notMeasured': [ + 'windows (requires Windows host)', + 'linux (requires Linux host)', + ], + }, + 'buildSize': { + 'sharedHarnessBundleBytes': int.parse(args[1]), + 'convexFlutterNativeFrameworkExecutableBytes': int.parse(args[2]), + 'sharedAppFrameworkExecutableBytes': int.parse(args[3]), + 'candidateIsolatedBundleBytes': null, + 'note': + 'The same harness bundle contains both adapters; only the native ' + 'convex_flutter framework is candidate-specific.', + }, + 'summary': { + for (final adapter in ['dartvex', 'convex_flutter']) + adapter: _summary( + samples.where((sample) => sample['adapter'] == adapter).toList(), + ), + }, + 'samples': samples, + }; + + stdout.writeln(const JsonEncoder.withIndent(' ').convert(output)); +} + +Map _summary(List> samples) { + const fields = [ + 'remoteConvergenceMs', + 'reconnectToLiveMs', + 'authFreshTokenAcceptedMs', + 'authRecoveryMs', + 'maxRssBytes', + 'averageProcessCpuPercent', + 'bytesSent', + 'bytesReceived', + 'transferredBytes', + 'runnerWallClockMs', + ]; + return { + for (final field in fields) + field: _statistics( + samples.map((sample) => (sample[field] as num).toDouble()).toList(), + ), + }; +} + +Map _statistics(List values) { + final sorted = [...values]..sort(); + final middle = sorted.length ~/ 2; + final median = sorted.length.isOdd + ? sorted[middle] + : (sorted[middle - 1] + sorted[middle]) / 2; + final p95Index = math.max(0, (0.95 * sorted.length).ceil() - 1); + return { + 'median': median, + 'p95': sorted[p95Index], + 'min': sorted.first, + 'max': sorted.last, + 'values': values, + }; +} + +_ProcessTiming _parseTime(String raw) { + double value(String label) => double.parse( + RegExp( + '^$label ([0-9.]+)\\s*\$', + multiLine: true, + ).firstMatch(raw)!.group(1)!, + ); + final rss = int.parse( + RegExp( + r'^\s*([0-9]+)\s+maximum resident set size\s*$', + multiLine: true, + ).firstMatch(raw)!.group(1)!, + ); + return _ProcessTiming( + realSeconds: value('real'), + userSeconds: value('user'), + systemSeconds: value('sys'), + maxRssBytes: rss, + ); +} + +final class _ProcessTiming { + const _ProcessTiming({ + required this.realSeconds, + required this.userSeconds, + required this.systemSeconds, + required this.maxRssBytes, + }); + + final double realSeconds; + final double userSeconds; + final double systemSeconds; + final int maxRssBytes; +} diff --git a/tool/convex_client_gauntlet/test/contract_gate_test.dart b/tool/convex_client_gauntlet/test/contract_gate_test.dart new file mode 100644 index 00000000..acec78be --- /dev/null +++ b/tool/convex_client_gauntlet/test/contract_gate_test.dart @@ -0,0 +1,75 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:icarus_convex_client_gauntlet/contract_gate.dart'; +import 'package:test/test.dart'; + +void main() { + test('strict Icarus wrapper repairs the Dartvex contract gate', () async { + final result = await evaluateContractGate(); + final report = result.report; + final checks = (report['checks']! as List) + .cast>(); + + Map check(String id) => + checks.singleWhere((item) => item['id'] == id); + + expect(check('function_rename')['status'], 'pass'); + expect(check('argument_rename')['status'], 'pass'); + expect(check('result_field_rename')['status'], 'pass'); + expect(check('result_field_rename')['generatedReturnType'], 'typed'); + expect(check('result_field_rename')['analysisExitCode'], isNonZero); + expect(check('missing_return_schema')['status'], 'pass'); + expect( + check('missing_return_schema')['diagnostics'], + contains(contains('listForParent has an unexpected dynamic result')), + ); + expect(check('unsupported_validator')['status'], 'pass'); + expect(check('unsupported_validator')['rawGenerationExitCode'], 0); + expect(check('unsupported_validator')['generationExitCode'], isNonZero); + expect( + check('unsupported_validator')['diagnostics'], + contains( + contains('folders.js:listForParent → returns → field "futureField"'), + ), + ); + expect(check('deterministic_regeneration')['status'], 'pass'); + expect( + check('deterministic_regeneration')['secondSha256'], + check('deterministic_regeneration')['firstSha256'], + ); + expect(report['gatePassed'], isTrue); + expect(report['decision'], 'continue_runtime_gauntlet'); + }); + + test('result mutation changes only the returned public id field', () { + Map functionFrom(String fixtureName) { + final fixture = + jsonDecode(File('fixtures/$fixtureName').readAsStringSync()) + as Map; + return (fixture['functions']! as List).single + as Map; + } + + Map resultFields(Map function) { + final returns = function['returns']! as Map; + final item = returns['value']! as Map; + return Map.from(item['value']! as Map); + } + + final baseline = functionFrom('folders_list_for_parent.json'); + final renamed = functionFrom('folders_result_renamed.json'); + expect(renamed['identifier'], baseline['identifier']); + expect(renamed['functionType'], baseline['functionType']); + expect(renamed['visibility'], baseline['visibility']); + expect(renamed['args'], baseline['args']); + + final baselineFields = resultFields(baseline); + final renamedFields = resultFields(renamed); + expect( + renamedFields.remove('folderPublicId'), + baselineFields.remove('publicId'), + ); + expect(renamedFields, baselineFields); + }); +} diff --git a/tool/icarus_convex_codegen/README.md b/tool/icarus_convex_codegen/README.md new file mode 100644 index 00000000..fee87430 --- /dev/null +++ b/tool/icarus_convex_codegen/README.md @@ -0,0 +1,31 @@ +# Icarus Convex code generator + +This standalone tool turns the committed, deployment-scrubbed Convex function +spec into Icarus-owned Dart. It does not contact a Convex deployment. + +From the repository root: + +```sh +fvm dart run tool/icarus_convex_codegen/bin/generate.dart +``` + +The generator resolves the annotated payload codecs in +`lib/collab/convex_payload_codecs.dart`, validates every public contract node, +and replaces the complete owned output set in `lib/collab/generated/`. + +When a public backend validator changes, refresh and audit the scrubbed +snapshots before regenerating: + +```sh +npm run snapshot:convex-contract +npm run audit:convex-contract +fvm dart run tool/icarus_convex_codegen/bin/generate.dart +git diff --exit-code -- convex/function_spec.json convex/error_codes.json lib/collab/generated +``` + +CI repeats generation from the committed snapshots on Windows and Linux. Its +contract job first creates an isolated Convex preview deployment, then runs the +snapshot command in check mode against that deployment. The repository must +have a Convex preview deploy key configured as the +`CONVEX_PREVIEW_DEPLOY_KEY` Actions secret; a production or ordinary deployment +key is intentionally rejected by `--preview-create`. diff --git a/tool/icarus_convex_codegen/analysis_options.yaml b/tool/icarus_convex_codegen/analysis_options.yaml new file mode 100644 index 00000000..4d8bd09e --- /dev/null +++ b/tool/icarus_convex_codegen/analysis_options.yaml @@ -0,0 +1,3 @@ +analyzer: + errors: + avoid_print: ignore diff --git a/tool/icarus_convex_codegen/bin/generate.dart b/tool/icarus_convex_codegen/bin/generate.dart new file mode 100644 index 00000000..0f3a1521 --- /dev/null +++ b/tool/icarus_convex_codegen/bin/generate.dart @@ -0,0 +1,57 @@ +import 'dart:io'; + +import 'package:icarus_convex_codegen/icarus_convex_codegen.dart'; +import 'package:path/path.dart' as path; + +Future main(List arguments) async { + try { + final repositoryRoot = _repositoryRoot(arguments); + final contract = parseContract( + functionSpecFile: File( + path.join(repositoryRoot, 'convex', 'function_spec.json'), + ), + errorCodesFile: File( + path.join(repositoryRoot, 'convex', 'error_codes.json'), + ), + ); + final payloadBindings = await scanPayloadBindings( + File( + path.join( + repositoryRoot, + 'lib', + 'collab', + 'convex_payload_codecs.dart', + ), + ), + ); + final mapped = mapContract(contract, payloadBindings); + final output = Directory( + path.join(repositoryRoot, 'lib', 'collab', 'generated'), + ); + final files = emitContract(mapped); + writeGeneratedOutput(output, files); + stdout.writeln( + 'Generated ${files.length} files for ${contract.functions.length} ' + 'public Convex functions.', + ); + } on ContractException catch (error) { + stderr.writeln(error.message); + exitCode = 1; + } on FormatException catch (error) { + stderr.writeln(error.message); + exitCode = 1; + } +} + +String _repositoryRoot(List arguments) { + if (arguments.isNotEmpty) { + if (arguments.length != 2 || arguments.first != '--repository-root') { + throw const FormatException( + 'Usage: dart run bin/generate.dart [--repository-root PATH]', + ); + } + return path.normalize(path.absolute(arguments[1])); + } + final packageRoot = path.dirname(path.dirname(Platform.script.toFilePath())); + return path.normalize(path.join(packageRoot, '..', '..')); +} diff --git a/tool/icarus_convex_codegen/lib/icarus_convex_codegen.dart b/tool/icarus_convex_codegen/lib/icarus_convex_codegen.dart new file mode 100644 index 00000000..48191735 --- /dev/null +++ b/tool/icarus_convex_codegen/lib/icarus_convex_codegen.dart @@ -0,0 +1,7 @@ +library; + +export 'src/contract.dart'; +export 'src/emitter.dart' show emitContract, writeGeneratedOutput; +export 'src/mapper.dart' show MappedContract, mapContract; +export 'src/parser.dart' show allowedPublicIds, parseContract; +export 'src/payload_bindings.dart' show scanPayloadBindings; diff --git a/tool/icarus_convex_codegen/lib/src/contract.dart b/tool/icarus_convex_codegen/lib/src/contract.dart new file mode 100644 index 00000000..02d0a0f4 --- /dev/null +++ b/tool/icarus_convex_codegen/lib/src/contract.dart @@ -0,0 +1,119 @@ +sealed class ConvexValidator { + const ConvexValidator(); +} + +final class NullValidator extends ConvexValidator { + const NullValidator(); +} + +final class BooleanValidator extends ConvexValidator { + const BooleanValidator(); +} + +final class NumberValidator extends ConvexValidator { + const NumberValidator(); +} + +final class BigIntValidator extends ConvexValidator { + const BigIntValidator(); +} + +final class StringValidator extends ConvexValidator { + const StringValidator(); +} + +final class BytesValidator extends ConvexValidator { + const BytesValidator(); +} + +final class LiteralValidator extends ConvexValidator { + const LiteralValidator(this.value); + + final Object? value; +} + +final class IdValidator extends ConvexValidator { + const IdValidator(this.tableName); + + final String tableName; +} + +final class ArrayValidator extends ConvexValidator { + const ArrayValidator(this.item); + + final ConvexValidator item; +} + +final class ObjectField { + const ObjectField({required this.validator, required this.optional}); + + final ConvexValidator validator; + final bool optional; +} + +final class ObjectValidator extends ConvexValidator { + const ObjectValidator(this.fields); + + final Map fields; +} + +final class RecordValidator extends ConvexValidator { + const RecordValidator({required this.keys, required this.values}); + + final ConvexValidator keys; + final ConvexValidator values; +} + +final class UnionValidator extends ConvexValidator { + const UnionValidator(this.members); + + final List members; +} + +enum ConvexFunctionKind { query, mutation, action } + +final class ConvexFunctionContract { + const ConvexFunctionContract({ + required this.identifier, + required this.moduleName, + required this.functionName, + required this.kind, + required this.args, + required this.result, + }); + + final String identifier; + final String moduleName; + final String functionName; + final ConvexFunctionKind kind; + final ObjectValidator args; + final ConvexValidator result; +} + +final class ConvexContract { + const ConvexContract({required this.functions, required this.errorCodes}); + + final List functions; + final List errorCodes; +} + +final class PayloadBinding { + const PayloadBinding({ + required this.tag, + required this.codecClass, + required this.dartType, + }); + + final String tag; + final String codecClass; + final String dartType; +} + +final class ContractException implements Exception { + const ContractException(this.message); + + final String message; + + @override + String toString() => message; +} diff --git a/tool/icarus_convex_codegen/lib/src/emitter.dart b/tool/icarus_convex_codegen/lib/src/emitter.dart new file mode 100644 index 00000000..ffb1c8b0 --- /dev/null +++ b/tool/icarus_convex_codegen/lib/src/emitter.dart @@ -0,0 +1,851 @@ +import 'dart:io'; + +import 'package:dart_style/dart_style.dart'; + +import 'contract.dart'; +import 'mapper.dart'; + +final _formatter = DartFormatter( + languageVersion: DartFormatter.latestLanguageVersion, +); + +Map emitContract(MappedContract contract) { + final files = { + 'convex_error_codes.dart': _format(_emitErrorCodes(contract.contract)), + 'convex_models.dart': _format(_emitModels(contract)), + 'icarus_convex_api.dart': _format(_emitApi(contract)), + 'generated.dart': _format(_emitBarrel()), + }; + return Map.unmodifiable(files); +} + +void writeGeneratedOutput(Directory output, Map files) { + if (output.existsSync()) output.deleteSync(recursive: true); + output.createSync(recursive: true); + final paths = files.keys.toList()..sort(); + for (final path in paths) { + File('${output.path}/$path').writeAsStringSync(files[path]!); + } +} + +String _format(String source) => _formatter.format(source); + +String _header() => ''' +// GENERATED CODE - DO NOT MODIFY BY HAND. +// Generated from convex/function_spec.json by tool/icarus_convex_codegen. +// ignore_for_file: prefer_const_constructors, unused_element, unused_import +'''; + +String _emitBarrel() => + ''' +${_header()} +export 'convex_error_codes.dart'; +export 'convex_models.dart'; +export 'icarus_convex_api.dart'; +'''; + +String _emitErrorCodes(ConvexContract contract) { + final out = StringBuffer() + ..writeln(_header()) + ..writeln("import '../transport/convex_transport.dart';") + ..writeln() + ..writeln('enum ConvexErrorCode {'); + for (final code in contract.errorCodes) { + out.writeln(" ${lowerCamelCase(code)}('${_escape(code)}'),"); + } + out + ..writeln(" unknown('UNKNOWN');") + ..writeln() + ..writeln(' const ConvexErrorCode(this.wireName);') + ..writeln(' final String wireName;') + ..writeln() + ..writeln(' static ConvexErrorCode fromWireName(String rawCode) {') + ..writeln(' for (final code in values) {') + ..writeln( + ' if (code != unknown && code.wireName == rawCode) return code;', + ) + ..writeln(' }') + ..writeln(' return unknown;') + ..writeln(' }') + ..writeln('}') + ..writeln() + ..writeln('final class ConvexFunctionException implements Exception {') + ..writeln(' const ConvexFunctionException({') + ..writeln(' required this.code,') + ..writeln(' required this.rawCode,') + ..writeln(' required this.message,') + ..writeln(' this.data,') + ..writeln(' });') + ..writeln() + ..writeln(' factory ConvexFunctionException.fromTransport(') + ..writeln(' ConvexTransportError error,') + ..writeln(' ) => ConvexFunctionException(') + ..writeln(' code: ConvexErrorCode.fromWireName(error.rawCode),') + ..writeln(' rawCode: error.rawCode,') + ..writeln(' message: error.message,') + ..writeln(' data: error.data,') + ..writeln(' );') + ..writeln() + ..writeln(' final ConvexErrorCode code;') + ..writeln(' final String rawCode;') + ..writeln(' final String message;') + ..writeln(' final ConvexValue? data;') + ..writeln() + ..writeln(" @override String toString() => 'ConvexFunctionException('") + ..writeln(" '\$rawCode, \$message)';") + ..writeln('}'); + return out.toString(); +} + +String _emitModels(MappedContract contract) { + final out = StringBuffer() + ..writeln(_header()) + ..writeln("import 'dart:typed_data';") + ..writeln() + ..writeln("import '../convex_payload_codecs.dart';") + ..writeln("import '../transport/convex_transport.dart';") + ..writeln() + ..writeln(_modelRuntime); + + final enums = [...contract.enums]..sort((a, b) => a.name.compareTo(b.name)); + for (final declaration in enums) { + _emitEnum(out, declaration); + } + + final unions = [...contract.unions]..sort((a, b) => a.name.compareTo(b.name)); + for (final declaration in unions) { + _emitUnion(out, declaration); + } + + final models = [...contract.models]..sort((a, b) => a.name.compareTo(b.name)); + for (final declaration in models) { + _emitModel(out, declaration); + } + + final opaqueNames = contract.opaqueUnions.keys.toList()..sort(); + for (final name in opaqueNames) { + _emitOpaqueUnion(out, name, contract.opaqueUnions[name]!); + } + + final rawEmitter = _RawPredicateEmitter(); + for (final declaration in contract.rawValidators) { + rawEmitter.registerRoot(declaration.name, declaration.validator); + } + out.writeln(rawEmitter.emit()); + _emitEndpointCodecs(out, contract.functions); + return out.toString(); +} + +void _emitEndpointCodecs(StringBuffer out, List functions) { + for (final function in functions) { + final prefix = _functionPrefix(function); + out.write('ConvexObject encode${prefix}Args('); + if (function.argsFields.isNotEmpty) { + out.write('{'); + for (final field in function.argsFields) { + if (field.optional) { + out.write( + 'ConvexOptional<${field.type.dartType}> ${field.dartName} = ' + 'const ConvexOptional.absent(),', + ); + } else { + out.write('required ${field.type.dartType} ${field.dartName},'); + } + } + out.write('}'); + } + out.writeln(') => ConvexObject({'); + for (final field in function.argsFields) { + final fieldPath = + "'${_escape(function.contract.identifier)}.args.${_escape(field.wireName)}'"; + if (field.optional) { + out.writeln( + " if (${field.dartName}.isPresent) '${_escape(field.wireName)}': ${field.type.encode('${field.dartName}.value', fieldPath)},", + ); + } else { + out.writeln( + " '${_escape(field.wireName)}': ${field.type.encode(field.dartName, fieldPath)},", + ); + } + } + out + ..writeln('});') + ..writeln() + ..writeln( + '${function.resultType.dartType} decode${prefix}Result(ConvexValue value) =>', + ) + ..writeln( + ' ${function.resultType.decode('value', "'${_escape(function.contract.identifier)}.returns'")};', + ) + ..writeln(); + } +} + +void _emitEnum(StringBuffer out, EnumDeclaration declaration) { + out.writeln('enum ${declaration.name} {'); + for (final value in declaration.values) { + out.writeln(" ${value.name}('${_escape(value.wireValue)}'),"); + } + out + ..writeln(' ;') + ..writeln() + ..writeln(' const ${declaration.name}(this.wireName);') + ..writeln(' final String wireName;') + ..writeln() + ..writeln( + ' static ${declaration.name} fromWireName(String wireName, String path) {', + ) + ..writeln(' for (final value in values) {') + ..writeln(' if (value.wireName == wireName) return value;') + ..writeln(' }') + ..writeln( + " throw ConvexDecodingException(path, 'unknown ${declaration.name} \$wireName');", + ) + ..writeln(' }') + ..writeln('}') + ..writeln(); +} + +void _emitUnion(StringBuffer out, UnionDeclaration declaration) { + out + ..writeln('sealed class ${declaration.name} {') + ..writeln(' const ${declaration.name}();') + ..writeln() + ..writeln( + ' factory ${declaration.name}.decode(ConvexValue value, String path) {', + ) + ..writeln(' final object = _decodeObject(value, path);') + ..writeln(' final discriminator = _decodeString(') + ..writeln( + " object.value['${_escape(declaration.discriminatorName)}'] ?? _missing(path, '${_escape(declaration.discriminatorName)}'),", + ) + ..writeln(" '\$path.${_escape(declaration.discriminatorName)}',") + ..writeln(' );') + ..writeln(' return switch (discriminator) {'); + for (final variant in declaration.variants) { + out.writeln( + " '${_escape(variant.discriminatorValue)}' => ${variant.model.name}.decode(value, path),", + ); + } + out + ..writeln( + " _ => throw ConvexDecodingException(path, 'unknown discriminator \$discriminator'),", + ) + ..writeln(' };') + ..writeln(' }') + ..writeln() + ..writeln(' ConvexObject encode(String path);') + ..writeln('}') + ..writeln(); +} + +void _emitModel(StringBuffer out, ModelDeclaration declaration) { + final inheritance = declaration.baseName == null + ? '' + : ' extends ${declaration.baseName}'; + out + ..writeln('final class ${declaration.name}$inheritance {') + ..write(' const ${declaration.name}({'); + for (final field in declaration.fields.where((field) => !field.optional)) { + out.write('required this.${field.dartName},'); + } + for (final field in declaration.fields.where((field) => field.optional)) { + out.write('this.${field.dartName} = const ConvexOptional.absent(),'); + } + out.writeln('});'); + for (final field in declaration.fields) { + final type = field.optional + ? 'ConvexOptional<${field.type.dartType}>' + : field.type.dartType; + out.writeln(' final $type ${field.dartName};'); + } + out + ..writeln() + ..writeln( + ' factory ${declaration.name}.decode(ConvexValue value, String path) {', + ) + ..writeln(' final object = _decodeObject(value, path);') + ..writeln(' _checkObjectFields(object, path, const {'); + if (declaration.discriminatorName != null) { + out.writeln(" '${_escape(declaration.discriminatorName!)}',"); + } + for (final field in declaration.fields) { + out.writeln(" '${_escape(field.wireName)}',"); + } + out + ..writeln(' });') + ..writeln(' return ${declaration.name}('); + for (final field in declaration.fields) { + final wire = _escape(field.wireName); + final fieldPath = "'\$path.$wire'"; + if (field.optional) { + out + ..writeln( + ' ${field.dartName}: object.value.containsKey(\'$wire\')', + ) + ..writeln(' ? ConvexOptional.present(') + ..writeln( + ' ${field.type.decode("object.value['$wire']!", fieldPath)},', + ) + ..writeln(' )') + ..writeln(' : const ConvexOptional.absent(),'); + } else { + out.writeln( + ' ${field.dartName}: ${field.type.decode("object.value['$wire'] ?? _missing(path, '$wire')", fieldPath)},', + ); + } + } + out + ..writeln(' );') + ..writeln(' }') + ..writeln() + ..writeln( + declaration.baseName == null + ? ' ConvexObject encode(String path) {' + : ' @override ConvexObject encode(String path) {', + ) + ..writeln(' return ConvexObject({'); + if (declaration.discriminatorName != null) { + out.writeln( + " '${_escape(declaration.discriminatorName!)}': ConvexString('${_escape(declaration.discriminatorValue!)}'),", + ); + } + for (final field in declaration.fields) { + final wire = _escape(field.wireName); + final fieldPath = "'\$path.$wire'"; + if (field.optional) { + out.writeln( + " if (${field.dartName}.isPresent) '$wire': ${field.type.encode('${field.dartName}.value', fieldPath)},", + ); + } else { + out.writeln( + " '$wire': ${field.type.encode(field.dartName, fieldPath)},", + ); + } + } + out + ..writeln(' });') + ..writeln(' }') + ..writeln('}') + ..writeln(); +} + +void _emitOpaqueUnion( + StringBuffer out, + String name, + List bindings, +) { + final dartType = bindings.first.dartType; + out + ..writeln('$dartType _decode$name(ConvexValue value, String path) {') + ..writeln(' final object = _decodeObject(value, path);') + ..writeln(" final tag = _decodeString(object.value['kind'] ??") + ..writeln(" _missing(path, 'kind'), '\$path.kind');") + ..writeln(' return switch (tag) {'); + for (final binding in bindings) { + out.writeln( + " '${_escape(binding.tag)}' => _decodePayload(() => const ${binding.codecClass}().decode(value), path),", + ); + } + out + ..writeln( + " _ => throw ConvexDecodingException('\$path.kind', 'unknown payload tag \$tag'),", + ) + ..writeln(' };') + ..writeln('}') + ..writeln() + ..writeln('ConvexValue _encode$name($dartType value, String path) {') + ..writeln(" final tag = value['kind'];") + ..writeln(' return switch (tag) {'); + for (final binding in bindings) { + out.writeln( + " '${_escape(binding.tag)}' => _encodePayload(() => const ${binding.codecClass}().encode(value), path),", + ); + } + out + ..writeln( + " _ => throw ConvexEncodingException('\$path.kind', 'unknown payload tag \$tag'),", + ) + ..writeln(' };') + ..writeln('}') + ..writeln(); +} + +String _emitApi(MappedContract contract) { + final functionsByModule = >{}; + for (final function in contract.functions) { + functionsByModule + .putIfAbsent(function.contract.moduleName, () => []) + .add(function); + } + final moduleNames = functionsByModule.keys.toList()..sort(); + final out = StringBuffer() + ..writeln(_header()) + ..writeln("import 'dart:async';") + ..writeln("import 'dart:typed_data';") + ..writeln() + ..writeln("import '../transport/convex_transport.dart';") + ..writeln("import 'convex_error_codes.dart';") + ..writeln("import 'convex_models.dart';") + ..writeln() + ..writeln(_queryRuntime) + ..writeln('abstract interface class IcarusConvexApi {') + ..writeln( + ' factory IcarusConvexApi(ConvexTransport transport) = _IcarusConvexApi;', + ); + for (final module in moduleNames) { + out.writeln(' ${pascalCase(module)}Module get ${lowerCamelCase(module)};'); + } + out + ..writeln('}') + ..writeln() + ..writeln('final class _IcarusConvexApi implements IcarusConvexApi {') + ..writeln(' _IcarusConvexApi(ConvexTransport transport)'); + if (moduleNames.isEmpty) { + out.writeln(' ;'); + } else { + out.writeln(' :'); + for (var index = 0; index < moduleNames.length; index += 1) { + final module = moduleNames[index]; + final ending = index == moduleNames.length - 1 ? ';' : ','; + out.writeln( + ' ${lowerCamelCase(module)} = _${pascalCase(module)}Module(transport)$ending', + ); + } + } + for (final module in moduleNames) { + out + ..writeln(' @override') + ..writeln( + ' final ${pascalCase(module)}Module ${lowerCamelCase(module)};', + ); + } + out + ..writeln('}') + ..writeln(); + + for (final module in moduleNames) { + final functions = functionsByModule[module]! + ..sort( + (left, right) => + left.contract.functionName.compareTo(right.contract.functionName), + ); + _emitModule(out, module, functions); + } + return out.toString(); +} + +void _emitModule( + StringBuffer out, + String module, + List functions, +) { + final moduleType = '${pascalCase(module)}Module'; + out.writeln('abstract interface class $moduleType {'); + for (final function in functions) { + out.writeln(' ${_methodSignature(function)};'); + } + out + ..writeln('}') + ..writeln() + ..writeln('final class _$moduleType implements $moduleType {') + ..writeln(' const _$moduleType(this._transport);') + ..writeln(' final ConvexTransport _transport;'); + for (final function in functions) { + out + ..writeln(' @override') + ..writeln(' ${_methodSignature(function)} {'); + final prefix = _functionPrefix(function); + out.write(' final args = encode${prefix}Args('); + if (function.argsFields.isNotEmpty) { + for (final field in function.argsFields) { + out.write('${field.dartName}: ${field.dartName},'); + } + } + out.writeln(');'); + final route = function.contract.identifier.replaceFirst('.js:', ':'); + final decode = 'decode${prefix}Result'; + switch (function.contract.kind) { + case ConvexFunctionKind.query: + out + ..writeln(' return ConvexQuery(') + ..writeln(' transport: _transport,') + ..writeln(" name: '${_escape(route)}',") + ..writeln(' args: args,') + ..writeln(' decode: $decode,') + ..writeln(' );'); + case ConvexFunctionKind.mutation: + out.writeln( + " return _invoke(() => _transport.mutation('${_escape(route)}', args), $decode);", + ); + case ConvexFunctionKind.action: + out.writeln( + " return _invoke(() => _transport.action('${_escape(route)}', args), $decode);", + ); + } + out.writeln(' }'); + } + out + ..writeln('}') + ..writeln(); +} + +String _functionPrefix(MappedFunction function) => + '${pascalCase(function.contract.moduleName)}' + '${pascalCase(function.contract.functionName)}'; + +String _methodSignature(MappedFunction function) { + final returnType = switch (function.contract.kind) { + ConvexFunctionKind.query => 'ConvexQuery<${function.resultType.dartType}>', + ConvexFunctionKind.mutation || + ConvexFunctionKind.action => 'Future<${function.resultType.dartType}>', + }; + final parameters = StringBuffer(); + if (function.argsFields.isNotEmpty) { + parameters.write('{'); + for (final field in function.argsFields) { + if (field.optional) { + parameters.write( + 'ConvexOptional<${field.type.dartType}> ${field.dartName} = ' + 'const ConvexOptional.absent(),', + ); + } else { + parameters.write('required ${field.type.dartType} ${field.dartName},'); + } + } + parameters.write('}'); + } + return '$returnType ${lowerCamelCase(function.contract.functionName)}($parameters)'; +} + +final class _RawPredicateEmitter { + final _nameBySignature = {}; + final _validatorByName = {}; + var _next = 0; + + void registerRoot(String name, ConvexValidator validator) { + _register(validator, preferredName: name); + } + + String _register(ConvexValidator validator, {String? preferredName}) { + final signature = validatorSignature(validator); + final existing = _nameBySignature[signature]; + if (existing != null) return existing; + final name = preferredName ?? '_matchesRaw${_next++}'; + _nameBySignature[signature] = name; + _validatorByName[name] = validator; + for (final child in _children(validator)) { + _register(child); + } + return name; + } + + String emit() { + final out = StringBuffer(); + for (final entry in _validatorByName.entries) { + out + ..writeln('bool ${entry.key}(ConvexValue value) =>') + ..writeln(' ${_predicate(entry.value, 'value')};') + ..writeln(); + } + return out.toString(); + } + + String _predicate(ConvexValidator validator, String input) { + String child(ConvexValidator validator, String childValue) { + final name = _nameBySignature[validatorSignature(validator)]!; + return '$name($childValue)'; + } + + return switch (validator) { + NullValidator() => '$input is ConvexNull', + BooleanValidator() => '$input is ConvexBoolean', + NumberValidator() => '($input is ConvexFloat || $input is ConvexInteger)', + BigIntValidator() => '$input is ConvexBigInt', + StringValidator() || IdValidator() => '$input is ConvexString', + BytesValidator() => '$input is ConvexBytes', + LiteralValidator(value: final literal) => _literalPredicate( + literal, + value: input, + ), + ArrayValidator(:final item) => + '$input is ConvexArray && $input.value.every((item) => ${child(item, 'item')})', + ObjectValidator(:final fields) => _objectPredicate(fields, input, child), + RecordValidator(:final keys, :final values) => + '$input is ConvexObject && $input.value.entries.every((entry) => ' + '${child(keys, 'ConvexString(entry.key)')} && ${child(values, 'entry.value')})', + UnionValidator(:final members) => + '(${members.map((member) => child(member, input)).join(' || ')})', + }; + } + + String _literalPredicate(Object? literal, {required String value}) { + if (literal == null) return '$value is ConvexNull'; + if (literal is String) { + return "$value is ConvexString && $value.value == '${_escape(literal)}'"; + } + if (literal is bool) { + return '$value is ConvexBoolean && $value.value == $literal'; + } + if (literal is num) { + return '($value is ConvexFloat && $value.value == $literal) || ' + '($value is ConvexInteger && $value.value == $literal)'; + } + throw ContractException('Unsupported raw literal $literal'); + } + + String _objectPredicate( + Map fields, + String value, + String Function(ConvexValidator, String) child, + ) { + final allowed = fields.keys.map((key) => "'${_escape(key)}'").join(','); + final checks = [ + '$value is ConvexObject', + '$value.value.keys.every(const {$allowed}.contains)', + ]; + for (final entry in fields.entries) { + final access = "$value.value['${_escape(entry.key)}']"; + if (entry.value.optional) { + checks.add( + '($access == null || ${child(entry.value.validator, '$access!')})', + ); + } else { + checks.add( + '($access != null && ${child(entry.value.validator, '$access!')})', + ); + } + } + return checks.join(' && '); + } + + Iterable _children(ConvexValidator validator) sync* { + switch (validator) { + case ArrayValidator(:final item): + yield item; + case ObjectValidator(:final fields): + for (final field in fields.values) yield field.validator; + case RecordValidator(:final keys, :final values): + yield keys; + yield values; + case UnionValidator(:final members): + yield* members; + default: + return; + } + } +} + +String _escape(String value) => value + .replaceAll(r'\', r'\\') + .replaceAll("'", r"\'") + .replaceAll(r'$', r'\$'); + +const _modelRuntime = r''' +final class ConvexOptional { + const ConvexOptional.absent() + : isPresent = false, + _value = null; + const ConvexOptional.present(T value) + : isPresent = true, + _value = value; + + final bool isPresent; + final T? _value; + + T get value { + if (!isPresent) throw StateError('Optional value is absent'); + return _value as T; + } +} + +final class ConvexDecodingException extends FormatException { + ConvexDecodingException(this.path, String message) + : super('$path: $message'); + final String path; +} + +final class ConvexEncodingException extends FormatException { + ConvexEncodingException(this.path, String message) + : super('$path: $message'); + final String path; +} + +Never _missing(String path, String field) => + throw ConvexDecodingException('$path.$field', 'missing required field'); + +String _fieldPath(String path, String field) => '$path.$field'; + +String _indexPath(String path, int index) => '$path[$index]'; + +void _checkObjectFields( + ConvexObject object, + String path, + Set allowed, +) { + for (final field in object.value.keys) { + if (!allowed.contains(field)) { + throw ConvexDecodingException('$path.$field', 'unexpected field'); + } + } +} + +Null _decodeNull(ConvexValue value, String path) { + if (value is ConvexNull) return null; + throw ConvexDecodingException(path, 'expected null'); +} + +bool _decodeBoolean(ConvexValue value, String path) { + if (value case ConvexBoolean(:final value)) return value; + throw ConvexDecodingException(path, 'expected boolean'); +} + +double _decodeNumber(ConvexValue value, String path) { + if (value case ConvexFloat(:final value)) return value; + if (value case ConvexInteger(:final value)) return value.toDouble(); + throw ConvexDecodingException(path, 'expected number'); +} + +ConvexValue _encodeNumber(double value, String path) { + return ConvexFloat(value); +} + +BigInt _decodeBigInt(ConvexValue value, String path) { + if (value case ConvexBigInt(:final value)) return value; + throw ConvexDecodingException(path, 'expected bigint'); +} + +String _decodeString(ConvexValue value, String path) { + if (value case ConvexString(:final value)) return value; + throw ConvexDecodingException(path, 'expected string'); +} + +Uint8List _decodeBytes(ConvexValue value, String path) { + if (value case ConvexBytes(:final value)) return Uint8List.fromList(value); + throw ConvexDecodingException(path, 'expected bytes'); +} + +ConvexArray _decodeArray(ConvexValue value, String path) { + if (value is ConvexArray) return value; + throw ConvexDecodingException(path, 'expected array'); +} + +ConvexObject _decodeObject(ConvexValue value, String path) { + if (value is ConvexObject) return value; + throw ConvexDecodingException(path, 'expected object'); +} + +T _expectLiteral(T value, T expected, String path) { + if (value == expected) return value; + throw ConvexDecodingException(path, 'expected literal $expected'); +} + +T _decodePayload(T Function() decode, String path) { + try { + return decode(); + } on FormatException catch (error) { + throw ConvexDecodingException(path, error.message); + } +} + +ConvexValue _encodePayload(ConvexValue Function() encode, String path) { + try { + return encode(); + } on FormatException catch (error) { + throw ConvexEncodingException(path, error.message); + } +} + +ConvexValue _decodeRaw( + ConvexValue value, + String path, + bool Function(ConvexValue) accepts, +) { + if (accepts(value)) return value; + throw ConvexDecodingException(path, 'value does not satisfy closed union'); +} +'''; + +const _queryRuntime = r''' +final class ConvexQuery { + const ConvexQuery({ + required ConvexTransport transport, + required String name, + required ConvexObject args, + required T Function(ConvexValue) decode, + }) : _transport = transport, + _name = name, + _args = args, + _decode = decode; + + final ConvexTransport _transport; + final String _name; + final ConvexObject _args; + final T Function(ConvexValue) _decode; + + Future fetch() => _invoke( + () => _transport.query(_name, _args), + _decode, + ); + + Stream watch() { + late final StreamController controller; + StreamSubscription? subscription; + var active = false; + + controller = StreamController( + onListen: () { + active = true; + subscription = _transport.subscribe(_name, _args).listen( + (value) { + if (!active) return; + try { + controller.add(_decode(value)); + } catch (error, stackTrace) { + active = false; + controller.addError(error, stackTrace); + subscription?.cancel(); + controller.close(); + } + }, + onError: (Object error, StackTrace stackTrace) { + if (!active) return; + if (error is ConvexTransportError) { + controller.addError( + ConvexFunctionException.fromTransport(error), + stackTrace, + ); + return; + } + active = false; + controller.addError(error, stackTrace); + subscription?.cancel(); + controller.close(); + }, + onDone: () { + if (!active) return; + active = false; + controller.close(); + }, + ); + }, + onCancel: () { + active = false; + return subscription?.cancel(); + }, + ); + return controller.stream; + } +} + +Future _invoke( + Future Function() invoke, + T Function(ConvexValue) decode, +) async { + try { + return decode(await invoke()); + } on ConvexTransportError catch (error) { + throw ConvexFunctionException.fromTransport(error); + } +} +'''; diff --git a/tool/icarus_convex_codegen/lib/src/mapper.dart b/tool/icarus_convex_codegen/lib/src/mapper.dart new file mode 100644 index 00000000..141d4a75 --- /dev/null +++ b/tool/icarus_convex_codegen/lib/src/mapper.dart @@ -0,0 +1,814 @@ +import 'dart:convert'; + +import 'contract.dart'; + +final class MappedContract { + const MappedContract({ + required this.contract, + required this.functions, + required this.models, + required this.enums, + required this.unions, + required this.rawValidators, + required this.opaqueUnions, + required this.payloadBindings, + }); + + final ConvexContract contract; + final List functions; + final List models; + final List enums; + final List unions; + final List rawValidators; + final Map> opaqueUnions; + final Map payloadBindings; +} + +final class MappedFunction { + const MappedFunction({ + required this.contract, + required this.argsFields, + required this.resultType, + }); + + final ConvexFunctionContract contract; + final List argsFields; + final MappedType resultType; +} + +final class MappedField { + const MappedField({ + required this.wireName, + required this.dartName, + required this.type, + required this.optional, + }); + + final String wireName; + final String dartName; + final MappedType type; + final bool optional; +} + +final class ModelDeclaration { + const ModelDeclaration({ + required this.name, + required this.fields, + required this.signature, + this.baseName, + this.discriminatorName, + this.discriminatorValue, + }); + + final String name; + final List fields; + final String signature; + final String? baseName; + final String? discriminatorName; + final String? discriminatorValue; +} + +final class EnumValueDeclaration { + const EnumValueDeclaration({required this.name, required this.wireValue}); + + final String name; + final String wireValue; +} + +final class EnumDeclaration { + const EnumDeclaration({ + required this.name, + required this.values, + required this.signature, + }); + + final String name; + final List values; + final String signature; +} + +final class UnionVariantDeclaration { + const UnionVariantDeclaration({ + required this.discriminatorValue, + required this.model, + }); + + final String discriminatorValue; + final ModelDeclaration model; +} + +final class UnionDeclaration { + const UnionDeclaration({ + required this.name, + required this.discriminatorName, + required this.variants, + required this.signature, + }); + + final String name; + final String discriminatorName; + final List variants; + final String signature; +} + +final class RawValidatorDeclaration { + const RawValidatorDeclaration({ + required this.name, + required this.validator, + required this.signature, + }); + + final String name; + final ConvexValidator validator; + final String signature; +} + +sealed class MappedType { + const MappedType(); + + String get dartType; + + String decode(String value, String path); + + String encode(String value, String path); +} + +final class PrimitiveMappedType extends MappedType { + const PrimitiveMappedType(this.kind); + + final String kind; + + @override + String get dartType => switch (kind) { + 'null' => 'Null', + 'boolean' => 'bool', + 'number' => 'double', + 'bigint' => 'BigInt', + 'string' || 'id' => 'String', + 'bytes' => 'Uint8List', + _ => throw StateError('Unknown primitive $kind'), + }; + + @override + String decode(String value, String path) => switch (kind) { + 'null' => '_decodeNull($value, $path)', + 'boolean' => '_decodeBoolean($value, $path)', + 'number' => '_decodeNumber($value, $path)', + 'bigint' => '_decodeBigInt($value, $path)', + 'string' || 'id' => '_decodeString($value, $path)', + 'bytes' => '_decodeBytes($value, $path)', + _ => throw StateError('Unknown primitive $kind'), + }; + + @override + String encode(String value, String path) => switch (kind) { + 'null' => 'const ConvexNull()', + 'boolean' => 'ConvexBoolean($value)', + 'number' => '_encodeNumber($value, $path)', + 'bigint' => 'ConvexBigInt($value)', + 'string' || 'id' => 'ConvexString($value)', + 'bytes' => 'ConvexBytes($value)', + _ => throw StateError('Unknown primitive $kind'), + }; +} + +final class NullableMappedType extends MappedType { + const NullableMappedType(this.inner); + + final MappedType inner; + + @override + String get dartType => '${inner.dartType}?'; + + @override + String decode(String value, String path) => + '($value) is ConvexNull ? null : ${inner.decode(value, path)}'; + + @override + String encode(String value, String path) => + '$value == null ? const ConvexNull() : ${inner.encode('$value!', path)}'; +} + +final class ListMappedType extends MappedType { + const ListMappedType(this.item); + + final MappedType item; + + @override + String get dartType => 'List<${item.dartType}>'; + + @override + String decode(String value, String path) => + '_decodeArray($value, $path).value.indexed.map((entry) => ' + '${item.decode('entry.\$2', '_indexPath($path, entry.\$1)')}).toList(growable: false)'; + + @override + String encode(String value, String path) => + 'ConvexArray($value.indexed.map((entry) => ' + '${item.encode('entry.\$2', '_indexPath($path, entry.\$1)')}).toList(growable: false))'; +} + +final class MapMappedType extends MappedType { + const MapMappedType(this.valueType); + + final MappedType valueType; + + @override + String get dartType => 'Map'; + + @override + String decode(String value, String path) => + 'Map.unmodifiable(_decodeObject($value, $path).value.map((key, item) => ' + 'MapEntry(key, ${valueType.decode('item', '_fieldPath($path, key)')})))'; + + @override + String encode(String value, String path) => + 'ConvexObject($value.map((key, item) => ' + 'MapEntry(key, ${valueType.encode('item', '_fieldPath($path, key)')})))'; +} + +final class DeclarationMappedType extends MappedType { + const DeclarationMappedType(this.name); + + final String name; + + @override + String get dartType => name; + + @override + String decode(String value, String path) => '$name.decode($value, $path)'; + + @override + String encode(String value, String path) => '$value.encode($path)'; +} + +final class EnumMappedType extends MappedType { + const EnumMappedType(this.name); + + final String name; + + @override + String get dartType => name; + + @override + String decode(String value, String path) => + '$name.fromWireName(_decodeString($value, $path), $path)'; + + @override + String encode(String value, String path) => 'ConvexString($value.wireName)'; +} + +final class LiteralMappedType extends MappedType { + const LiteralMappedType(this.value, this.inner); + + final Object? value; + final MappedType inner; + + @override + String get dartType => inner.dartType; + + @override + String decode(String input, String path) { + final decoded = inner.decode(input, path); + return '_expectLiteral($decoded, ${jsonEncode(value)}, $path)'; + } + + @override + String encode(String input, String path) => + inner.encode('_expectLiteral($input, ${jsonEncode(value)}, $path)', path); +} + +final class OpaqueMappedType extends MappedType { + const OpaqueMappedType(this.binding); + + final PayloadBinding binding; + + @override + String get dartType => binding.dartType; + + @override + String decode(String value, String path) => + '_decodePayload(() => const ${binding.codecClass}().decode($value), $path)'; + + @override + String encode(String value, String path) => + '_encodePayload(() => const ${binding.codecClass}().encode($value), $path)'; +} + +final class OpaqueUnionMappedType extends MappedType { + const OpaqueUnionMappedType({required this.dartTypeName, required this.name}); + + final String dartTypeName; + final String name; + + @override + String get dartType => dartTypeName; + + @override + String decode(String value, String path) => '_decode$name($value, $path)'; + + @override + String encode(String value, String path) => '_encode$name($value, $path)'; +} + +final class RawMappedType extends MappedType { + const RawMappedType(this.validatorName); + + final String validatorName; + + @override + String get dartType => 'ConvexValue'; + + @override + String decode(String value, String path) => + '_decodeRaw($value, $path, $validatorName)'; + + @override + String encode(String value, String path) => + '_decodeRaw($value, $path, $validatorName)'; +} + +MappedContract mapContract( + ConvexContract contract, + Map payloadBindings, +) { + final mapper = _ContractMapper(contract, payloadBindings); + return mapper.map(); +} + +final class _ContractMapper { + _ContractMapper(this.contract, this.payloadBindings); + + final ConvexContract contract; + final Map payloadBindings; + final models = []; + final enums = []; + final unions = []; + final rawValidators = []; + final _modelBySignature = {}; + final _enumBySignature = {}; + final _unionBySignature = {}; + final _rawBySignature = {}; + final _declarationSignatures = {}; + final _usedPayloadTags = {}; + + MappedContract map() { + final mappedFunctions = []; + for (final function in contract.functions) { + final prefix = + '${pascalCase(function.moduleName)}${pascalCase(function.functionName)}'; + final argsFields = _mapObjectFields(function.args, '${prefix}Args'); + final resultType = _mapType(function.result, '${prefix}Result'); + mappedFunctions.add( + MappedFunction( + contract: function, + argsFields: argsFields, + resultType: resultType, + ), + ); + } + final missingBindings = payloadBindings.keys.toSet() + ..removeAll(_usedPayloadTags); + if (missingBindings.isNotEmpty) { + final sorted = missingBindings.toList()..sort(); + throw ContractException('Unused payload bindings: ${sorted.join(', ')}'); + } + return MappedContract( + contract: contract, + functions: mappedFunctions, + models: List.unmodifiable(models), + enums: List.unmodifiable(enums), + unions: List.unmodifiable(unions), + rawValidators: List.unmodifiable(rawValidators), + opaqueUnions: Map.unmodifiable(_opaqueUnions), + payloadBindings: payloadBindings, + ); + } + + MappedType _mapType(ConvexValidator validator, String suggestedName) { + return switch (validator) { + NullValidator() => const PrimitiveMappedType('null'), + BooleanValidator() => const PrimitiveMappedType('boolean'), + NumberValidator() => const PrimitiveMappedType('number'), + BigIntValidator() => const PrimitiveMappedType('bigint'), + StringValidator() => const PrimitiveMappedType('string'), + BytesValidator() => const PrimitiveMappedType('bytes'), + IdValidator() => const PrimitiveMappedType('id'), + LiteralValidator(:final value) => _mapLiteral(value, suggestedName), + ArrayValidator(:final item) => ListMappedType( + _mapType(item, '${suggestedName}Item'), + ), + ObjectValidator() => _mapObject(validator, suggestedName), + RecordValidator(:final keys, :final values) => _mapRecord( + keys, + values, + suggestedName, + ), + UnionValidator() => _mapUnion(validator, suggestedName), + }; + } + + MappedType _mapLiteral(Object? value, String suggestedName) { + if (value is String) { + return _mapEnum([value], suggestedName); + } + if (value == null) return const PrimitiveMappedType('null'); + if (value is bool) { + return LiteralMappedType(value, const PrimitiveMappedType('boolean')); + } + if (value is num) { + return LiteralMappedType(value, const PrimitiveMappedType('number')); + } + throw ContractException('$suggestedName has unsupported literal $value'); + } + + MappedType _mapObject(ObjectValidator validator, String suggestedName) { + final payloadTag = _payloadTag(validator); + if (payloadTag != null) { + final binding = payloadBindings[payloadTag]; + if (binding == null) { + throw ContractException('Missing payload binding for tag $payloadTag'); + } + _usedPayloadTags.add(payloadTag); + return OpaqueMappedType(binding); + } + final signature = validatorSignature(validator); + final existing = _modelBySignature[signature]; + if (existing != null) return DeclarationMappedType(existing.name); + final name = pascalCase(suggestedName); + _claimName(name, signature); + final fields = _mapObjectFields(validator, name); + final declaration = ModelDeclaration( + name: name, + fields: fields, + signature: signature, + ); + _modelBySignature[signature] = declaration; + models.add(declaration); + return DeclarationMappedType(name); + } + + List _mapObjectFields( + ObjectValidator validator, + String suggestedName, { + String? excludedField, + }) { + final fields = []; + final usedNames = {}; + for (final entry in validator.fields.entries) { + if (entry.key == excludedField) continue; + final dartName = lowerCamelCase(entry.key); + if (!usedNames.add(dartName)) { + throw ContractException( + '$suggestedName has colliding field names at ${entry.key}', + ); + } + fields.add( + MappedField( + wireName: entry.key, + dartName: dartName, + type: _mapType( + entry.value.validator, + '$suggestedName${pascalCase(entry.key)}', + ), + optional: entry.value.optional, + ), + ); + } + return List.unmodifiable(fields); + } + + MappedType _mapRecord( + ConvexValidator keys, + ConvexValidator values, + String suggestedName, + ) { + if (keys is! StringValidator) { + throw ContractException('$suggestedName record keys must be strings'); + } + return MapMappedType(_mapType(values, '${suggestedName}Value')); + } + + MappedType _mapUnion(UnionValidator validator, String suggestedName) { + final withoutNull = validator.members + .where((member) => member is! NullValidator) + .toList(); + final nullable = withoutNull.length != validator.members.length; + if (nullable && withoutNull.length == 1) { + return NullableMappedType(_mapType(withoutNull.single, suggestedName)); + } + if (withoutNull.every((member) => member is LiteralValidator)) { + final values = withoutNull + .cast() + .map((member) => member.value) + .toList(); + if (values.every((value) => value is String)) { + final mapped = _mapEnum(values.cast(), suggestedName); + return nullable ? NullableMappedType(mapped) : mapped; + } + } + if (withoutNull.every((member) => member is ObjectValidator)) { + final objectMembers = withoutNull.cast(); + final payloadTags = objectMembers.map(_payloadTag).toList(); + if (payloadTags.every((tag) => tag != null)) { + final bindings = payloadTags.map((tag) { + final binding = payloadBindings[tag]; + if (binding == null) { + throw ContractException('Missing payload binding for tag $tag'); + } + _usedPayloadTags.add(tag!); + return binding; + }).toList(); + final dartTypes = bindings.map((binding) => binding.dartType).toSet(); + if (dartTypes.length != 1) { + throw ContractException( + '$suggestedName payload union has incompatible Dart types', + ); + } + final name = pascalCase(suggestedName); + final mapped = OpaqueUnionMappedType( + dartTypeName: dartTypes.single, + name: name, + ); + _registerOpaqueUnion(name, bindings); + return nullable ? NullableMappedType(mapped) : mapped; + } + final discriminator = _findDiscriminator(objectMembers); + if (discriminator != null) { + final mapped = _mapDiscriminatedUnion( + validator, + objectMembers, + discriminator, + suggestedName, + ); + return nullable ? NullableMappedType(mapped) : mapped; + } + } + final signature = validatorSignature(validator); + final existing = _rawBySignature[signature]; + if (existing != null) return RawMappedType(existing.name); + final name = '_validate${pascalCase(suggestedName)}'; + _claimName(name, signature); + final declaration = RawValidatorDeclaration( + name: name, + validator: validator, + signature: signature, + ); + _rawBySignature[signature] = declaration; + rawValidators.add(declaration); + return RawMappedType(name); + } + + MappedType _mapEnum(List values, String suggestedName) { + final sorted = {...values}.toList()..sort(); + if (sorted.length != values.length) { + throw ContractException('$suggestedName has duplicate literal members'); + } + final signature = 'enum:${sorted.map(jsonEncode).join(',')}'; + final existing = _enumBySignature[signature]; + if (existing != null) return EnumMappedType(existing.name); + final name = pascalCase(suggestedName); + _claimName(name, signature); + final usedNames = {}; + final enumValues = []; + for (final wireValue in sorted) { + final enumName = lowerCamelCase(wireValue); + if (!usedNames.add(enumName)) { + throw ContractException('$name has colliding enum member $wireValue'); + } + enumValues.add( + EnumValueDeclaration(name: enumName, wireValue: wireValue), + ); + } + final declaration = EnumDeclaration( + name: name, + values: enumValues, + signature: signature, + ); + _enumBySignature[signature] = declaration; + enums.add(declaration); + return EnumMappedType(name); + } + + MappedType _mapDiscriminatedUnion( + UnionValidator validator, + List members, + String discriminator, + String suggestedName, + ) { + final signature = validatorSignature(validator); + final existing = _unionBySignature[signature]; + if (existing != null) return DeclarationMappedType(existing.name); + final name = pascalCase(suggestedName); + _claimName(name, signature); + final variants = []; + for (final member in members) { + final literal = + member.fields[discriminator]!.validator as LiteralValidator; + final wireValue = literal.value! as String; + final variantName = '$name${pascalCase(wireValue)}'; + final modelSignature = '${validatorSignature(member)}:base=$name'; + _claimName(variantName, modelSignature); + final model = ModelDeclaration( + name: variantName, + fields: _mapObjectFields( + member, + variantName, + excludedField: discriminator, + ), + signature: modelSignature, + baseName: name, + discriminatorName: discriminator, + discriminatorValue: wireValue, + ); + models.add(model); + variants.add( + UnionVariantDeclaration(discriminatorValue: wireValue, model: model), + ); + } + final declaration = UnionDeclaration( + name: name, + discriminatorName: discriminator, + variants: variants, + signature: signature, + ); + _unionBySignature[signature] = declaration; + unions.add(declaration); + return DeclarationMappedType(name); + } + + final _opaqueUnions = >{}; + + void _registerOpaqueUnion(String name, List bindings) { + final signature = bindings.map((binding) => binding.tag).join('|'); + final previous = _declarationSignatures[name]; + if (previous != null && previous != 'opaque:$signature') { + throw ContractException('Generated declaration name collision at $name'); + } + _declarationSignatures[name] = 'opaque:$signature'; + _opaqueUnions[name] = bindings; + } + + String? _payloadTag(ObjectValidator validator) { + final kind = validator.fields['kind']; + final version = validator.fields['payloadVersion']; + final data = validator.fields['data']; + if (kind == null || version == null || data == null) return null; + if (kind.optional || version.optional || data.optional) return null; + if (kind.validator case LiteralValidator(value: final String tag)) { + if (version.validator is NumberValidator && + data.validator is RecordValidator) { + return tag; + } + } + return null; + } + + String? _findDiscriminator(List members) { + final preferred = ['type', 'status', 'kind', 'targetType', 'provider']; + for (final name in preferred) { + final values = {}; + var valid = true; + for (final member in members) { + final field = member.fields[name]; + if (field == null || field.optional) { + valid = false; + break; + } + if (field.validator case LiteralValidator(value: final String value)) { + if (!values.add(value)) valid = false; + } else { + valid = false; + } + } + if (valid) return name; + } + return null; + } + + void _claimName(String name, String signature) { + final existing = _declarationSignatures[name]; + if (existing != null && existing != signature) { + throw ContractException('Generated declaration name collision at $name'); + } + _declarationSignatures[name] = signature; + } +} + +String validatorSignature(ConvexValidator validator) => switch (validator) { + NullValidator() => 'null', + BooleanValidator() => 'boolean', + NumberValidator() => 'number', + BigIntValidator() => 'bigint', + StringValidator() => 'string', + BytesValidator() => 'bytes', + LiteralValidator(:final value) => 'literal:${jsonEncode(value)}', + IdValidator(:final tableName) => 'id:$tableName', + ArrayValidator(:final item) => 'array:${validatorSignature(item)}', + ObjectValidator(:final fields) => + 'object:{${fields.entries.map((entry) => '${jsonEncode(entry.key)}:${entry.value.optional ? '?' : '!'}${validatorSignature(entry.value.validator)}').join(',')}}', + RecordValidator(:final keys, :final values) => + 'record:${validatorSignature(keys)}:${validatorSignature(values)}', + UnionValidator(:final members) => + 'union:[${members.map(validatorSignature).join(',')}]', +}; + +const _dartKeywords = { + 'abstract', + 'as', + 'assert', + 'async', + 'await', + 'base', + 'break', + 'case', + 'catch', + 'class', + 'const', + 'continue', + 'covariant', + 'default', + 'deferred', + 'do', + 'dynamic', + 'else', + 'enum', + 'export', + 'extends', + 'extension', + 'external', + 'factory', + 'false', + 'final', + 'finally', + 'for', + 'Function', + 'get', + 'hide', + 'if', + 'implements', + 'import', + 'in', + 'interface', + 'is', + 'late', + 'library', + 'mixin', + 'new', + 'null', + 'of', + 'on', + 'operator', + 'part', + 'required', + 'rethrow', + 'return', + 'sealed', + 'set', + 'show', + 'static', + 'super', + 'switch', + 'sync', + 'this', + 'throw', + 'true', + 'try', + 'typedef', + 'var', + 'void', + 'when', + 'while', + 'with', + 'yield', +}; + +String pascalCase(String value) { + final words = _words(value); + if (words.isEmpty) throw ContractException('Cannot map empty Dart name'); + return words + .map((word) => '${word[0].toUpperCase()}${word.substring(1)}') + .join(); +} + +String lowerCamelCase(String value) { + final pascal = pascalCase(value); + var result = '${pascal[0].toLowerCase()}${pascal.substring(1)}'; + if (_dartKeywords.contains(result)) result = '${result}Value'; + return result; +} + +List _words(String value) { + final separated = value + .replaceAllMapped( + RegExp(r'([a-z0-9])([A-Z])'), + (match) => '${match[1]} ${match[2]}', + ) + .replaceAll(RegExp(r'[^A-Za-z0-9]+'), ' ') + .trim(); + if (separated.isEmpty) return const []; + return separated + .split(RegExp(r'\s+')) + .map((word) => word.toLowerCase()) + .toList(); +} diff --git a/tool/icarus_convex_codegen/lib/src/parser.dart b/tool/icarus_convex_codegen/lib/src/parser.dart new file mode 100644 index 00000000..fad76c8b --- /dev/null +++ b/tool/icarus_convex_codegen/lib/src/parser.dart @@ -0,0 +1,197 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'contract.dart'; + +const allowedPublicIds = { + 'images.js:completeUpload.args.storageId:_storage', +}; + +ConvexContract parseContract({ + required File functionSpecFile, + required File errorCodesFile, +}) { + final decodedSpec = jsonDecode(functionSpecFile.readAsStringSync()); + if (decodedSpec is! Map || + decodedSpec.length != 1 || + decodedSpec['functions'] is! List) { + throw const ContractException( + 'function_spec.json must contain only a functions array', + ); + } + final functions = []; + final identifiers = {}; + for (final rawFunction in decodedSpec['functions'] as List) { + if (rawFunction is! Map) { + throw const ContractException('Function entries must be objects'); + } + final function = Map.from(rawFunction); + final visibility = function['visibility']; + if (visibility is! Map || visibility['kind'] != 'public') continue; + final identifier = function['identifier']; + if (identifier is! String || !identifiers.add(identifier)) { + throw ContractException('Duplicate or invalid function $identifier'); + } + final routeMatch = RegExp( + r'^([A-Za-z][A-Za-z0-9_]*)\.js:([A-Za-z][A-Za-z0-9_]*)$', + ).firstMatch(identifier); + if (routeMatch == null) { + throw ContractException('Invalid public function identifier $identifier'); + } + final args = function['args']; + final result = function['returns']; + if (args == null) { + throw ContractException('$identifier.args is missing a validator'); + } + if (result == null) { + throw ContractException('$identifier.returns is missing a validator'); + } + final parsedArgs = _parseValidator(args, '$identifier.args', identifier); + if (parsedArgs is! ObjectValidator) { + throw ContractException('$identifier.args must be an object validator'); + } + functions.add( + ConvexFunctionContract( + identifier: identifier, + moduleName: routeMatch.group(1)!, + functionName: routeMatch.group(2)!, + kind: switch (function['functionType']) { + 'Query' => ConvexFunctionKind.query, + 'Mutation' => ConvexFunctionKind.mutation, + 'Action' => ConvexFunctionKind.action, + final value => throw ContractException( + '$identifier has unknown function type $value', + ), + }, + args: parsedArgs, + result: _parseValidator(result, '$identifier.returns', identifier), + ), + ); + } + functions.sort((left, right) => left.identifier.compareTo(right.identifier)); + + final decodedCodes = jsonDecode(errorCodesFile.readAsStringSync()); + if (decodedCodes is! List || decodedCodes.any((code) => code is! String)) { + throw const ContractException('error_codes.json must be a string array'); + } + final errorCodes = decodedCodes.cast(); + final expectedCodes = {...errorCodes}.toList()..sort(); + if (expectedCodes.length != errorCodes.length || + !_sameStrings(expectedCodes, errorCodes)) { + throw const ContractException( + 'error_codes.json must be sorted without duplicates', + ); + } + return ConvexContract(functions: functions, errorCodes: errorCodes); +} + +ConvexValidator _parseValidator(Object? raw, String path, String identifier) { + if (raw is! Map) { + throw ContractException('$path is not a validator object'); + } + final validator = Map.from(raw); + return switch (validator['type']) { + 'null' => const NullValidator(), + 'boolean' => const BooleanValidator(), + 'number' => const NumberValidator(), + 'bigint' || 'int64' => const BigIntValidator(), + 'string' => const StringValidator(), + 'bytes' => const BytesValidator(), + 'literal' => LiteralValidator(validator['value']), + 'id' => _parseId(validator, path, identifier), + 'array' => ArrayValidator( + _parseValidator(validator['value'], '$path.item', identifier), + ), + 'object' => _parseObject(validator, path, identifier), + 'record' => _parseRecord(validator, path, identifier), + 'union' => _parseUnion(validator, path, identifier), + 'any' => throw ContractException('$path uses forbidden validator any'), + final type => throw ContractException( + '$path uses unsupported validator ${type ?? 'null'}', + ), + }; +} + +IdValidator _parseId( + Map validator, + String path, + String identifier, +) { + final tableName = validator['tableName']; + if (tableName is! String) { + throw ContractException('$path has an invalid id table'); + } + final allowlistKey = '$path:$tableName'; + if (!allowedPublicIds.contains(allowlistKey)) { + throw ContractException('$path exposes Convex id $tableName'); + } + return IdValidator(tableName); +} + +ObjectValidator _parseObject( + Map validator, + String path, + String identifier, +) { + final rawFields = validator['value']; + if (rawFields is! Map) { + throw ContractException('$path has invalid object fields'); + } + final fields = {}; + for (final entry in rawFields.entries) { + if (entry.key is! String || entry.value is! Map) { + throw ContractException('$path has an invalid object field'); + } + final field = Map.from(entry.value as Map); + if (field['optional'] is! bool || field['fieldType'] == null) { + throw ContractException('$path.${entry.key} has an invalid field'); + } + fields[entry.key as String] = ObjectField( + validator: _parseValidator( + field['fieldType'], + '$path.${entry.key}', + identifier, + ), + optional: field['optional'] as bool, + ); + } + return ObjectValidator(Map.unmodifiable(fields)); +} + +RecordValidator _parseRecord( + Map validator, + String path, + String identifier, +) { + final values = validator['values']; + if (values is! Map || values['fieldType'] == null) { + throw ContractException('$path has invalid record values'); + } + return RecordValidator( + keys: _parseValidator(validator['keys'], '$path.key', identifier), + values: _parseValidator(values['fieldType'], '$path.value', identifier), + ); +} + +UnionValidator _parseUnion( + Map validator, + String path, + String identifier, +) { + final rawMembers = validator['value']; + if (rawMembers is! List || rawMembers.isEmpty) { + throw ContractException('$path has an empty union'); + } + return UnionValidator([ + for (var index = 0; index < rawMembers.length; index += 1) + _parseValidator(rawMembers[index], '$path.union$index', identifier), + ]); +} + +bool _sameStrings(List left, List right) { + if (left.length != right.length) return false; + for (var index = 0; index < left.length; index += 1) { + if (left[index] != right[index]) return false; + } + return true; +} diff --git a/tool/icarus_convex_codegen/lib/src/payload_bindings.dart b/tool/icarus_convex_codegen/lib/src/payload_bindings.dart new file mode 100644 index 00000000..41225cdc --- /dev/null +++ b/tool/icarus_convex_codegen/lib/src/payload_bindings.dart @@ -0,0 +1,73 @@ +import 'dart:io'; + +import 'package:analyzer/dart/analysis/analysis_context_collection.dart'; +import 'package:analyzer/dart/analysis/results.dart'; +import 'package:analyzer/dart/ast/ast.dart'; + +import 'contract.dart'; + +Future> scanPayloadBindings(File sourceFile) async { + final sourcePath = sourceFile.absolute.path; + final collection = AnalysisContextCollection(includedPaths: [sourcePath]); + final session = collection.contextFor(sourcePath).currentSession; + final result = await session.getResolvedUnit(sourcePath); + if (result is! ResolvedUnitResult) { + throw ContractException('Unable to resolve ${sourceFile.path}'); + } + final analysisErrors = result.errors + .where((diagnostic) => diagnostic.severity.name == 'ERROR') + .toList(); + if (analysisErrors.isNotEmpty) { + throw ContractException( + 'Payload codec library does not analyze: ${analysisErrors.join('; ')}', + ); + } + + final bindings = {}; + for (final declaration + in result.unit.declarations.whereType()) { + Annotation? payloadAnnotation; + for (final annotation in declaration.metadata) { + if (annotation.name.name == 'ConvexPayload') { + payloadAnnotation = annotation; + break; + } + } + if (payloadAnnotation == null) continue; + final arguments = payloadAnnotation.arguments?.arguments; + if (arguments == null || + arguments.length != 1 || + arguments.single is! SimpleStringLiteral) { + throw ContractException( + '${declaration.name.lexeme} must use @ConvexPayload with one string tag', + ); + } + final tag = (arguments.single as SimpleStringLiteral).value; + String? dartType; + final interfaces = declaration.implementsClause?.interfaces ?? const []; + for (final interface in interfaces) { + if (interface.name2.lexeme != 'ConvexPayloadCodec') continue; + final typeArguments = interface.typeArguments?.arguments; + if (typeArguments == null || typeArguments.length != 1) { + throw ContractException( + '${declaration.name.lexeme} must implement ConvexPayloadCodec', + ); + } + dartType = typeArguments.single.toSource(); + } + if (dartType == null) { + throw ContractException( + '${declaration.name.lexeme} must directly implement ConvexPayloadCodec', + ); + } + if (bindings.containsKey(tag)) { + throw ContractException('Duplicate payload binding for tag $tag'); + } + bindings[tag] = PayloadBinding( + tag: tag, + codecClass: declaration.name.lexeme, + dartType: dartType, + ); + } + return Map.unmodifiable(bindings); +} diff --git a/tool/icarus_convex_codegen/pubspec.lock b/tool/icarus_convex_codegen/pubspec.lock new file mode 100644 index 00000000..1905206b --- /dev/null +++ b/tool/icarus_convex_codegen/pubspec.lock @@ -0,0 +1,397 @@ +# Generated by pub +# See https://dart.dev/tools/pub/glossary#lockfile +packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + url: "https://pub.dev" + source: hosted + version: "85.0.0" + analyzer: + dependency: "direct main" + description: + name: analyzer + sha256: f4ad0fea5f102201015c9aae9d93bc02f75dd9491529a8c21f88d17a8523d44c + url: "https://pub.dev" + source: hosted + version: "7.6.0" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" + async: + dependency: transitive + description: + name: async + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.dev" + source: hosted + version: "2.13.1" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" + collection: + dependency: transitive + description: + name: collection + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" + source: hosted + version: "1.19.1" + convert: + dependency: transitive + description: + name: convert + sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 + url: "https://pub.dev" + source: hosted + version: "3.1.2" + coverage: + dependency: transitive + description: + name: coverage + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" + url: "https://pub.dev" + source: hosted + version: "1.15.1" + crypto: + dependency: transitive + description: + name: crypto + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.dev" + source: hosted + version: "3.0.7" + dart_style: + dependency: "direct main" + description: + name: dart_style + sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + url: "https://pub.dev" + source: hosted + version: "3.1.1" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" + frontend_server_client: + dependency: transitive + description: + name: frontend_server_client + sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 + url: "https://pub.dev" + source: hosted + version: "4.0.0" + glob: + dependency: transitive + description: + name: glob + sha256: "218aeb56050c714f62a3182775320dfa04602b55074873e24e31bbd39bda96fb" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 + url: "https://pub.dev" + source: hosted + version: "3.2.2" + http_parser: + dependency: transitive + description: + name: http_parser + sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" + url: "https://pub.dev" + source: hosted + version: "4.1.2" + io: + dependency: transitive + description: + name: io + sha256: "2635216ca6a737e60de577ffa1a48a0bec76ca8a62917cfc1bb88c14c570646f" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + js: + dependency: transitive + description: + name: js + sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc" + url: "https://pub.dev" + source: hosted + version: "0.7.2" + logging: + dependency: transitive + description: + name: logging + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 + url: "https://pub.dev" + source: hosted + version: "1.3.0" + matcher: + dependency: transitive + description: + name: matcher + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" + source: hosted + version: "0.12.17" + meta: + dependency: transitive + description: + name: meta + sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9" + url: "https://pub.dev" + source: hosted + version: "1.19.0" + mime: + dependency: transitive + description: + name: mime + sha256: bd47de35f07e27267e69c8c8b22edf9473bfee170a60d60fcc93730c5144b7f6 + url: "https://pub.dev" + source: hosted + version: "2.1.0" + node_preamble: + dependency: transitive + description: + name: node_preamble + sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" + url: "https://pub.dev" + source: hosted + version: "2.0.2" + package_config: + dependency: transitive + description: + name: package_config + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc + url: "https://pub.dev" + source: hosted + version: "2.2.0" + path: + dependency: "direct main" + description: + name: path + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" + source: hosted + version: "1.9.1" + pool: + dependency: transitive + description: + name: pool + sha256: "4177f68c237ea2128d1bee66ac17b2ce05ba3dbaafcbdd54c5d40a39d0b6b11c" + url: "https://pub.dev" + source: hosted + version: "1.5.3" + pub_semver: + dependency: transitive + description: + name: pub_semver + sha256: "261236774e8b1d69cfc6b9eabbc96c40f25e7a2d6b171f3385d4f65d5734fb24" + url: "https://pub.dev" + source: hosted + version: "2.2.1" + shelf: + dependency: transitive + description: + name: shelf + sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 + url: "https://pub.dev" + source: hosted + version: "1.4.2" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" + url: "https://pub.dev" + source: hosted + version: "3.0.2" + shelf_static: + dependency: transitive + description: + name: shelf_static + sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 + url: "https://pub.dev" + source: hosted + version: "1.1.3" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" + url: "https://pub.dev" + source: hosted + version: "3.0.0" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + source_maps: + dependency: transitive + description: + name: source_maps + sha256: "14c2945847669b44089bb1222f66873d7ff7103c58911917f2a63c5a62327898" + url: "https://pub.dev" + source: hosted + version: "0.10.14" + source_span: + dependency: transitive + description: + name: source_span + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.dev" + source: hosted + version: "1.10.2" + stack_trace: + dependency: transitive + description: + name: stack_trace + sha256: "277654b3034d17ac6f9f1cb5595db011b1d5d41e8806866db28e0abaa101c490" + url: "https://pub.dev" + source: hosted + version: "1.12.2" + stream_channel: + dependency: transitive + description: + name: stream_channel + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" + source: hosted + version: "2.1.4" + string_scanner: + dependency: transitive + description: + name: string_scanner + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" + source: hosted + version: "1.4.1" + term_glyph: + dependency: transitive + description: + name: term_glyph + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" + source: hosted + version: "1.2.2" + test: + dependency: "direct dev" + description: + name: test + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" + url: "https://pub.dev" + source: hosted + version: "1.26.3" + test_api: + dependency: transitive + description: + name: test_api + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + url: "https://pub.dev" + source: hosted + version: "0.7.7" + test_core: + dependency: transitive + description: + name: test_core + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" + url: "https://pub.dev" + source: hosted + version: "0.6.12" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 + url: "https://pub.dev" + source: hosted + version: "1.4.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: "5f37239c4851efcef929cea7824e76df7f2f0970aef85d66bbc430afa40e72f0" + url: "https://pub.dev" + source: hosted + version: "15.3.0" + watcher: + dependency: transitive + description: + name: watcher + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" + url: "https://pub.dev" + source: hosted + version: "1.0.1" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 + url: "https://pub.dev" + source: hosted + version: "3.0.3" + webkit_inspection_protocol: + dependency: transitive + description: + name: webkit_inspection_protocol + sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" + url: "https://pub.dev" + source: hosted + version: "1.2.1" + yaml: + dependency: transitive + description: + name: yaml + sha256: f67cdd8e07d3c6329146aaef1ba043542b3134c12489f553ca9a7435d1068aea + url: "https://pub.dev" + source: hosted + version: "3.1.4" +sdks: + dart: ">=3.11.0 <4.0.0" diff --git a/tool/icarus_convex_codegen/pubspec.yaml b/tool/icarus_convex_codegen/pubspec.yaml new file mode 100644 index 00000000..116cd28f --- /dev/null +++ b/tool/icarus_convex_codegen/pubspec.yaml @@ -0,0 +1,13 @@ +name: icarus_convex_codegen +publish_to: none + +environment: + sdk: ">=3.11.0 <4.0.0" + +dependencies: + analyzer: 7.6.0 + dart_style: 3.1.1 + path: 1.9.1 + +dev_dependencies: + test: 1.26.3 diff --git a/tool/icarus_convex_codegen/test/fixtures/error_codes.json b/tool/icarus_convex_codegen/test/fixtures/error_codes.json new file mode 100644 index 00000000..89dfaeee --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/error_codes.json @@ -0,0 +1,4 @@ +[ + "CONFLICT", + "UNAUTHENTICATED" +] diff --git a/tool/icarus_convex_codegen/test/fixtures/missing_return.json b/tool/icarus_convex_codegen/test/fixtures/missing_return.json new file mode 100644 index 00000000..d5242d5a --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/missing_return.json @@ -0,0 +1,11 @@ +{ + "functions": [ + { + "args": { "type": "object", "value": {} }, + "functionType": "Query", + "identifier": "fixture.js:missingReturn", + "returns": null, + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/icarus_convex_codegen/test/fixtures/name_collision.json b/tool/icarus_convex_codegen/test/fixtures/name_collision.json new file mode 100644 index 00000000..f4e8ebc2 --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/name_collision.json @@ -0,0 +1,11 @@ +{ + "functions": [ + { + "args": { "type": "object", "value": { "foo-bar": { "fieldType": { "type": "string" }, "optional": false }, "foo_bar": { "fieldType": { "type": "string" }, "optional": false } } }, + "functionType": "Query", + "identifier": "fixture.js:nameCollision", + "returns": { "type": "null" }, + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/icarus_convex_codegen/test/fixtures/positive_all_nodes.json b/tool/icarus_convex_codegen/test/fixtures/positive_all_nodes.json new file mode 100644 index 00000000..2fe6d044 --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/positive_all_nodes.json @@ -0,0 +1,33 @@ +{ + "functions": [ + { + "args": { + "type": "object", + "value": { + "arrayValue": { "fieldType": { "type": "array", "value": { "type": "string" } }, "optional": false }, + "bigintValue": { "fieldType": { "type": "bigint" }, "optional": false }, + "booleanValue": { "fieldType": { "type": "boolean" }, "optional": false }, + "bytesValue": { "fieldType": { "type": "bytes" }, "optional": false }, + "literalValue": { "fieldType": { "type": "literal", "value": "fixed" }, "optional": false }, + "nullValue": { "fieldType": { "type": "null" }, "optional": false }, + "numberValue": { "fieldType": { "type": "number" }, "optional": false }, + "objectValue": { "fieldType": { "type": "object", "value": { "name": { "fieldType": { "type": "string" }, "optional": false } } }, "optional": false }, + "optionalValue": { "fieldType": { "type": "string" }, "optional": true }, + "recordValue": { "fieldType": { "type": "record", "keys": { "type": "string" }, "values": { "fieldType": { "type": "number" }, "optional": false } }, "optional": false }, + "stringValue": { "fieldType": { "type": "string" }, "optional": false }, + "unionValue": { "fieldType": { "type": "union", "value": [ { "type": "literal", "value": "first" }, { "type": "literal", "value": "second" } ] }, "optional": false } + } + }, + "functionType": "Query", + "identifier": "fixture.js:allNodes", + "returns": { + "type": "union", + "value": [ + { "type": "object", "value": { "kind": { "fieldType": { "type": "literal", "value": "ready" }, "optional": false }, "value": { "fieldType": { "type": "string" }, "optional": false } } }, + { "type": "object", "value": { "kind": { "fieldType": { "type": "literal", "value": "waiting" }, "optional": false }, "remaining": { "fieldType": { "type": "number" }, "optional": false } } } + ] + }, + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/icarus_convex_codegen/test/fixtures/public_id.json b/tool/icarus_convex_codegen/test/fixtures/public_id.json new file mode 100644 index 00000000..57ce3024 --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/public_id.json @@ -0,0 +1,11 @@ +{ + "functions": [ + { + "args": { "type": "object", "value": { "documentId": { "fieldType": { "type": "id", "tableName": "strategies" }, "optional": false } } }, + "functionType": "Query", + "identifier": "fixture.js:publicId", + "returns": { "type": "null" }, + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/icarus_convex_codegen/test/fixtures/unsupported_validator.json b/tool/icarus_convex_codegen/test/fixtures/unsupported_validator.json new file mode 100644 index 00000000..af7cac37 --- /dev/null +++ b/tool/icarus_convex_codegen/test/fixtures/unsupported_validator.json @@ -0,0 +1,11 @@ +{ + "functions": [ + { + "args": { "type": "object", "value": { "future": { "fieldType": { "type": "future-validator" }, "optional": false } } }, + "functionType": "Query", + "identifier": "fixture.js:unsupported", + "returns": { "type": "null" }, + "visibility": { "kind": "public" } + } + ] +} diff --git a/tool/icarus_convex_codegen/test/generator_test.dart b/tool/icarus_convex_codegen/test/generator_test.dart new file mode 100644 index 00000000..cd35e4da --- /dev/null +++ b/tool/icarus_convex_codegen/test/generator_test.dart @@ -0,0 +1,477 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:icarus_convex_codegen/icarus_convex_codegen.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + final packageRoot = Directory.current.absolute; + final fixtures = Directory(path.join(packageRoot.path, 'test', 'fixtures')); + final errorCodes = File(path.join(fixtures.path, 'error_codes.json')); + + test( + 'positive fixture maps every supported schema node deterministically', + () { + final contract = parseContract( + functionSpecFile: File( + path.join(fixtures.path, 'positive_all_nodes.json'), + ), + errorCodesFile: errorCodes, + ); + final first = emitContract(mapContract(contract, const {})); + final second = emitContract(mapContract(contract, const {})); + + expect(first, equals(second)); + expect(first.keys, { + 'convex_error_codes.dart', + 'convex_models.dart', + 'generated.dart', + 'icarus_convex_api.dart', + }); + expect(first['convex_models.dart'], contains('Uint8List')); + expect(first['convex_models.dart'], contains('BigInt')); + expect( + first['convex_models.dart'], + contains('sealed class FixtureAllNodesResult'), + ); + expect(first['icarus_convex_api.dart'], contains('ConvexQuery<')); + }, + ); + + test('positive fixture generates analyzable standalone libraries', () async { + final contract = parseContract( + functionSpecFile: File( + path.join(fixtures.path, 'positive_all_nodes.json'), + ), + errorCodesFile: errorCodes, + ); + final directory = Directory.systemTemp.createTempSync('icarus-analyze-'); + addTearDown(() => directory.deleteSync(recursive: true)); + final generated = Directory( + path.join(directory.path, 'lib', 'collab', 'generated'), + ); + writeGeneratedOutput( + generated, + emitContract(mapContract(contract, const {})), + ); + _writeAnalysisStubs(directory, packageRoot); + final analysis = await Process.run(Platform.resolvedExecutable, [ + 'analyze', + path.join(directory.path, 'lib'), + ]); + expect( + analysis.exitCode, + 0, + reason: '${analysis.stdout}${analysis.stderr}', + ); + }); + + test('negative fixtures fail with their golden diagnostic', () { + final goldenLines = File( + path.join(packageRoot.path, 'test', 'goldens', 'failures.txt'), + ).readAsLinesSync(); + for (final line in goldenLines) { + final separator = line.indexOf('|'); + final fixtureName = line.substring(0, separator); + final expected = line.substring(separator + 1); + expect( + () { + final contract = parseContract( + functionSpecFile: File(path.join(fixtures.path, fixtureName)), + errorCodesFile: errorCodes, + ); + mapContract(contract, const {}); + }, + throwsA( + isA().having( + (error) => error.message, + fixtureName, + expected, + ), + ), + ); + } + }); + + test('any is rejected explicitly', () { + final directory = Directory.systemTemp.createTempSync('icarus-any-'); + addTearDown(() => directory.deleteSync(recursive: true)); + final spec = File(path.join(directory.path, 'spec.json')) + ..writeAsStringSync( + jsonEncode( + _singleFunction( + args: _object({ + 'value': _field({'type': 'any'}), + }), + result: {'type': 'null'}, + ), + ), + ); + expect( + () => parseContract(functionSpecFile: spec, errorCodesFile: errorCodes), + throwsA( + isA().having( + (error) => error.message, + 'message', + contains('uses forbidden validator any'), + ), + ), + ); + }); + + test( + 'payload scan resolves generic type and rejects duplicate tags', + () async { + final directory = Directory.systemTemp.createTempSync('icarus-codecs-'); + addTearDown(() => directory.deleteSync(recursive: true)); + final valid = File(path.join(directory.path, 'valid.dart')) + ..writeAsStringSync(_codecSource(secondTag: 'drawing')); + final bindings = await scanPayloadBindings(valid); + expect(bindings['agent']?.dartType, 'Map'); + expect(bindings['drawing']?.codecClass, 'SecondCodec'); + + final duplicate = File(path.join(directory.path, 'duplicate.dart')) + ..writeAsStringSync(_codecSource(secondTag: 'agent')); + await expectLater( + scanPayloadBindings(duplicate), + throwsA( + isA().having( + (error) => error.message, + 'message', + 'Duplicate payload binding for tag agent', + ), + ), + ); + }, + ); + + test('unbound and unused payload tags fail closed', () { + final contract = ConvexContract( + functions: [ + ConvexFunctionContract( + identifier: 'fixture.js:payload', + moduleName: 'fixture', + functionName: 'payload', + kind: ConvexFunctionKind.query, + args: const ObjectValidator({}), + result: _payloadEnvelope('agent'), + ), + ], + errorCodes: const [], + ); + expect( + () => mapContract(contract, const {}), + throwsA( + isA().having( + (error) => error.message, + 'message', + 'Missing payload binding for tag agent', + ), + ), + ); + expect( + () => mapContract( + const ConvexContract(functions: [], errorCodes: []), + const { + 'agent': PayloadBinding( + tag: 'agent', + codecClass: 'AgentCodec', + dartType: 'Object', + ), + }, + ), + throwsA( + isA().having( + (error) => error.message, + 'message', + 'Unused payload bindings: agent', + ), + ), + ); + }); + + test('owned output recreation removes stale files', () { + final directory = Directory.systemTemp.createTempSync('icarus-output-'); + addTearDown(() => directory.deleteSync(recursive: true)); + File(path.join(directory.path, 'stale.dart')) + ..createSync() + ..writeAsStringSync('stale'); + writeGeneratedOutput(directory, const {'current.dart': 'current\n'}); + expect(File(path.join(directory.path, 'stale.dart')).existsSync(), isFalse); + expect( + File(path.join(directory.path, 'current.dart')).readAsStringSync(), + 'current\n', + ); + }); + + test('contract mutations change output or stop generation', () { + final baselineSpec = _mutationSpec(); + final baseline = _emitInMemory(baselineSpec, ['CONFLICT']); + + final mutations = >[ + _deepCopy(baselineSpec) + ..['functions'] = [ + ...((baselineSpec['functions'] as List).cast>()) + .map( + (function) => {...function, 'identifier': 'sample.js:renamed'}, + ), + ], + _renameField(baselineSpec, area: 'args', from: 'name', to: 'title'), + _renameField(baselineSpec, area: 'returns', from: 'name', to: 'title'), + _replaceEnumMember(baselineSpec, 'first', 'second'), + ]; + for (final mutation in mutations) { + expect(_emitInMemory(mutation, ['CONFLICT']), isNot(equals(baseline))); + } + expect(_emitInMemory(baselineSpec, ['FORBIDDEN']), isNot(equals(baseline))); + }); + + test('breaking mutations fail an unchanged typed caller', () async { + final directory = Directory.systemTemp.createTempSync('icarus-caller-'); + addTearDown(() => directory.deleteSync(recursive: true)); + final generated = Directory( + path.join(directory.path, 'lib', 'collab', 'generated'), + ); + _writeAnalysisStubs(directory, packageRoot); + final caller = File(path.join(directory.path, 'bin', 'caller.dart')) + ..createSync(recursive: true) + ..writeAsStringSync(''' +import '../lib/collab/generated/generated.dart'; + +Future unchangedCaller(IcarusConvexApi api) async { + final result = await api.sample + .read(name: 'name', mode: SampleReadArgsMode.first) + .fetch(); + if (ConvexErrorCode.conflict.wireName.isEmpty) throw StateError('code'); + return result.name; +} +'''); + + Future analyzeWith( + Map spec, + List codes, + ) async { + writeGeneratedOutput(generated, _emitInMemory(spec, codes)); + return Process.run(Platform.resolvedExecutable, [ + 'analyze', + caller.path, + generated.path, + ]); + } + + final baselineSpec = _mutationSpec(); + final baseline = await analyzeWith(baselineSpec, ['CONFLICT']); + expect( + baseline.exitCode, + 0, + reason: '${baseline.stdout}${baseline.stderr}', + ); + + final mutations = <({Map spec, List codes})>[ + ( + spec: _deepCopy(baselineSpec) + ..['functions'] = [ + ...((baselineSpec['functions'] as List) + .cast>()) + .map( + (function) => { + ...function, + 'identifier': 'sample.js:renamed', + }, + ), + ], + codes: ['CONFLICT'], + ), + ( + spec: _renameField( + baselineSpec, + area: 'args', + from: 'name', + to: 'title', + ), + codes: ['CONFLICT'], + ), + ( + spec: _renameField( + baselineSpec, + area: 'returns', + from: 'name', + to: 'title', + ), + codes: ['CONFLICT'], + ), + ( + spec: _replaceEnumMember(baselineSpec, 'first', 'second'), + codes: ['CONFLICT'], + ), + (spec: baselineSpec, codes: ['FORBIDDEN']), + ]; + for (final mutation in mutations) { + final result = await analyzeWith(mutation.spec, mutation.codes); + expect( + result.exitCode, + isNot(0), + reason: 'Mutation unexpectedly preserved the caller:\n${result.stdout}', + ); + } + }); +} + +void _writeAnalysisStubs(Directory directory, Directory packageRoot) { + final transport = File( + path.join( + directory.path, + 'lib', + 'collab', + 'transport', + 'convex_transport.dart', + ), + )..createSync(recursive: true); + transport.writeAsStringSync( + File( + path.normalize( + path.join( + packageRoot.path, + '..', + '..', + 'lib', + 'collab', + 'transport', + 'convex_transport.dart', + ), + ), + ).readAsStringSync(), + ); + File(path.join(directory.path, 'lib', 'collab', 'convex_payload_codecs.dart')) + ..createSync(recursive: true) + ..writeAsStringSync('// No opaque payloads in this fixture.\n'); +} + +Map _emitInMemory( + Map spec, + List codes, +) { + final directory = Directory.systemTemp.createTempSync('icarus-mutation-'); + try { + final specFile = File(path.join(directory.path, 'spec.json')) + ..writeAsStringSync(jsonEncode(spec)); + final codesFile = File(path.join(directory.path, 'codes.json')) + ..writeAsStringSync(jsonEncode(codes)); + return emitContract( + mapContract( + parseContract(functionSpecFile: specFile, errorCodesFile: codesFile), + const {}, + ), + ); + } finally { + directory.deleteSync(recursive: true); + } +} + +Map _mutationSpec() => _singleFunction( + identifier: 'sample.js:read', + args: _object({ + 'mode': _field({ + 'type': 'union', + 'value': [ + {'type': 'literal', 'value': 'first'}, + {'type': 'literal', 'value': 'last'}, + ], + }), + 'name': _field({'type': 'string'}), + }), + result: _object({ + 'name': _field({'type': 'string'}), + }), +); + +Map _renameField( + Map source, { + required String area, + required String from, + required String to, +}) { + final copy = _deepCopy(source); + final function = ((copy['functions'] as List).single as Map); + final validator = function[area] as Map; + final fields = validator['value'] as Map; + fields[to] = fields.remove(from); + return copy; +} + +Map _replaceEnumMember( + Map source, + String from, + String to, +) { + final copy = _deepCopy(source); + final function = ((copy['functions'] as List).single as Map); + final args = function['args'] as Map; + final mode = + ((args['value'] as Map)['mode'] + as Map)['fieldType'] + as Map; + final members = mode['value'] as List; + for (final member in members.cast>()) { + if (member['value'] == from) member['value'] = to; + } + return copy; +} + +Map _deepCopy(Map source) => + Map.from(jsonDecode(jsonEncode(source)) as Map); + +Map _singleFunction({ + String identifier = 'fixture.js:test', + required Map args, + required Map result, +}) => { + 'functions': [ + { + 'args': args, + 'functionType': 'Query', + 'identifier': identifier, + 'returns': result, + 'visibility': {'kind': 'public'}, + }, + ], +}; + +Map _object(Map fields) => { + 'type': 'object', + 'value': fields, +}; + +Map _field(Map validator) => { + 'fieldType': validator, + 'optional': false, +}; + +ObjectValidator _payloadEnvelope(String tag) => ObjectValidator({ + 'kind': ObjectField(validator: LiteralValidator(tag), optional: false), + 'payloadVersion': const ObjectField( + validator: NumberValidator(), + optional: false, + ), + 'data': const ObjectField( + validator: RecordValidator( + keys: StringValidator(), + values: StringValidator(), + ), + optional: false, + ), +}); + +String _codecSource({required String secondTag}) => + ''' +class ConvexPayload { + const ConvexPayload(this.tag); + final String tag; +} +abstract interface class ConvexPayloadCodec {} +@ConvexPayload('agent') +final class AgentCodec implements ConvexPayloadCodec> {} +@ConvexPayload('$secondTag') +final class SecondCodec implements ConvexPayloadCodec> {} +'''; diff --git a/tool/icarus_convex_codegen/test/goldens/failures.txt b/tool/icarus_convex_codegen/test/goldens/failures.txt new file mode 100644 index 00000000..90a969d1 --- /dev/null +++ b/tool/icarus_convex_codegen/test/goldens/failures.txt @@ -0,0 +1,4 @@ +missing_return.json|fixture.js:missingReturn.returns is missing a validator +unsupported_validator.json|fixture.js:unsupported.args.future uses unsupported validator future-validator +public_id.json|fixture.js:publicId.args.documentId exposes Convex id strategies +name_collision.json|FixtureNameCollisionArgs has colliding field names at foo_bar diff --git a/tool/snapshot_convex_contract.mjs b/tool/snapshot_convex_contract.mjs new file mode 100644 index 00000000..4cfcdd11 --- /dev/null +++ b/tool/snapshot_convex_contract.mjs @@ -0,0 +1,87 @@ +import { execFileSync } from "node:child_process"; +import { readFileSync, writeFileSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; + +const toolDirectory = dirname(fileURLToPath(import.meta.url)); +const repositoryRoot = resolve(toolDirectory, ".."); +const functionSpecPath = resolve(repositoryRoot, "convex/function_spec.json"); +const errorCodesPath = resolve(repositoryRoot, "convex/error_codes.json"); +const errorsSourcePath = resolve(repositoryRoot, "convex/lib/errors.ts"); +const checkOnly = process.argv.includes("--check"); + +function sortObjectKeys(value) { + if (Array.isArray(value)) { + return value.map(sortObjectKeys); + } + if (value === null || typeof value !== "object") { + return value; + } + return Object.fromEntries( + Object.entries(value) + .sort(([left], [right]) => left.localeCompare(right)) + .map(([key, child]) => [key, sortObjectKeys(child)]), + ); +} + +function readFunctionSpec() { + const executable = process.platform === "win32" ? "npx.cmd" : "npx"; + const args = ["convex", "function-spec"]; + const previewName = process.env.CONVEX_PREVIEW_NAME; + if (previewName) { + args.push("--preview-name", previewName); + } + const raw = execFileSync(executable, args, { + cwd: repositoryRoot, + encoding: "utf8", + maxBuffer: 64 * 1024 * 1024, + stdio: ["ignore", "pipe", "inherit"], + }); + const liveSpec = JSON.parse(raw); + return sortObjectKeys({ + functions: [...liveSpec.functions].sort((left, right) => + left.identifier.localeCompare(right.identifier), + ), + }); +} + +function readErrorCodes() { + const source = readFileSync(errorsSourcePath, "utf8"); + const declaration = source.match( + /export const errorCodes\s*=\s*\[([\s\S]*?)\]\s*as const/, + ); + if (declaration === null) { + throw new Error("convex/lib/errors.ts must export errorCodes as a const array"); + } + const codes = [...declaration[1].matchAll(/"([A-Z][A-Z0-9_]*)"/g)].map( + (match) => match[1], + ); + if (codes.length === 0) { + throw new Error("errorCodes must contain at least one code"); + } + return [...new Set(codes)].sort(); +} + +function asJson(value) { + return `${JSON.stringify(value, null, 2)}\n`; +} + +function writeOrCheck(path, contents) { + if (!checkOnly) { + writeFileSync(path, contents); + return; + } + const committed = readFileSync(path, "utf8"); + if (committed !== contents) { + throw new Error(`${path} is stale; run npm run snapshot:convex-contract`); + } +} + +writeOrCheck(functionSpecPath, asJson(readFunctionSpec())); +writeOrCheck(errorCodesPath, asJson(readErrorCodes())); + +console.log( + checkOnly + ? "Convex contract snapshots are current." + : "Wrote scrubbed Convex contract snapshots.", +); diff --git a/tsconfig.json b/tsconfig.json index be3d138c..ec8d8ff8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -25,5 +25,6 @@ "noUnusedLocals": false, "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false - } + }, + "exclude": ["node_modules", "third_party"] }