@happyvertical/smrt-mobile-contract 0.38.21 → 0.38.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -203,9 +203,11 @@ export declare interface MobileContractOptions {
203
203
  }
204
204
 
205
205
  /**
206
- * Response of `GET /api/mobile/session` the app-boot payload for a stored
207
- * bearer. `extras` is an app-defined JSON object escape hatch (must remain a
208
- * JSON object; the Kotlin side decodes it as `JsonObject`).
206
+ * Response of `GET /api/mobile/session` -- the app-boot payload for a stored
207
+ * bearer. `extras` is the only app-defined extension point and must remain a
208
+ * JSON object (the Kotlin side decodes it as `JsonObject`). App-domain fields
209
+ * placed at the top level are outside the contract and are ignored by the
210
+ * Kotlin decoder.
209
211
  */
210
212
  export declare interface MobileSessionBootstrap {
211
213
  user: MobileUserSummary;
package/dist/index.js CHANGED
@@ -377,8 +377,10 @@ import kotlinx.serialization.json.JsonObject
377
377
  *
378
378
  * The server owns the OIDC/PKCE exchange: \`auth/start\` returns the
379
379
  * authorization URL plus the \`state\`/\`codeVerifier\` the client must persist
380
- * and echo back on \`auth/complete\`. Reference implementation: anytown
381
- * dashboard; SMRT-shipped handlers are tracked in issue #1748.
380
+ * and echo back on \`auth/complete\`. SMRT ships the SvelteKit handlers in
381
+ * @happyvertical/smrt-users (issue #1748). App-defined session bootstrap data
382
+ * belongs under MobileSessionBootstrap.extras; unknown top-level fields are
383
+ * outside this contract and are ignored by the Kotlin client.
382
384
  */
383
385
 
384
386
  @Serializable
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/contract-version.ts","../src/identifiers.ts","../src/build-contract.ts","../src/emit-framework.ts","../src/emit-kotlin.ts","../src/emit-swift.ts","../src/file-set.ts","../src/framework-types.ts"],"sourcesContent":["/** Schema version of the mobile-contract.json envelope. */\nexport const SMRT_MOBILE_CONTRACT_SCHEMA_VERSION = 1;\n\n/**\n * Version stamp of the framework contract surface (auth/session/device/pack\n * localization types). Bump when the framework contract shape changes.\n */\nexport const SMRT_MOBILE_CONTRACT_VERSION = '2026-07-01.v1';\n","/**\n * Identifier + literal escaping for generated Kotlin/Swift source. Manifest\n * field names are valid JS identifiers, but may collide with Kotlin/Swift\n * keywords; string defaults may contain characters that Kotlin string\n * templates or escapes treat specially.\n */\n\nconst KOTLIN_HARD_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n]);\n\nconst SWIFT_KEYWORDS = new Set([\n 'associatedtype',\n 'as',\n 'break',\n 'case',\n 'catch',\n 'class',\n 'continue',\n 'default',\n 'defer',\n 'deinit',\n 'do',\n 'else',\n 'enum',\n 'extension',\n 'fallthrough',\n 'false',\n 'fileprivate',\n 'for',\n 'func',\n 'guard',\n 'if',\n 'import',\n 'in',\n 'init',\n 'inout',\n 'internal',\n 'is',\n 'let',\n 'nil',\n 'open',\n 'operator',\n 'private',\n 'protocol',\n 'public',\n 'repeat',\n 'rethrows',\n 'return',\n 'self',\n 'static',\n 'struct',\n 'subscript',\n 'super',\n 'switch',\n 'throw',\n 'throws',\n 'true',\n 'try',\n 'typealias',\n 'var',\n 'where',\n 'while',\n]);\n\nconst PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** Backtick-escapes Kotlin hard keywords (and non-plain identifiers). */\nexport function kotlinIdentifier(name: string): string {\n return KOTLIN_HARD_KEYWORDS.has(name) || !PLAIN_IDENTIFIER.test(name)\n ? `\\`${name}\\``\n : name;\n}\n\n/** Backtick-escapes Swift keywords (and non-plain identifiers). */\nexport function swiftIdentifier(name: string): string {\n return SWIFT_KEYWORDS.has(name) || !PLAIN_IDENTIFIER.test(name)\n ? `\\`${name}\\``\n : name;\n}\n\n/**\n * Emits a Kotlin double-quoted string literal. Escapes `$` (string-template\n * interpolation), quotes, backslashes, and control characters — JSON escaping\n * is NOT Kotlin-safe (`$` stays raw and `\\f` is not a Kotlin escape).\n */\nexport function kotlinStringLiteral(value: string): string {\n let out = '\"';\n for (const char of value) {\n switch (char) {\n case '\\\\':\n out += '\\\\\\\\';\n break;\n case '\"':\n out += '\\\\\"';\n break;\n case '$':\n out += '\\\\$';\n break;\n case '\\n':\n out += '\\\\n';\n break;\n case '\\r':\n out += '\\\\r';\n break;\n case '\\t':\n out += '\\\\t';\n break;\n default: {\n const code = char.codePointAt(0) ?? 0;\n // Control chars and lone surrogates must be \\u-escaped: a raw lone\n // surrogate would be written to disk as U+FFFD, silently corrupting\n // the literal.\n out +=\n code < 0x20 || (code >= 0xd800 && code <= 0xdfff)\n ? `\\\\u${code.toString(16).padStart(4, '0')}`\n : char;\n }\n }\n }\n return `${out}\"`;\n}\n","import { createHash } from 'node:crypto';\nimport {\n SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n SMRT_MOBILE_CONTRACT_VERSION,\n} from './contract-version.js';\nimport { kotlinStringLiteral } from './identifiers.js';\nimport type {\n MobileContract,\n MobileContractField,\n MobileContractObject,\n MobileContractOptions,\n SmrtManifestField,\n SmrtManifestObject,\n} from './types.js';\n\n/**\n * SMRT manifest field type → Kotlin type. Decimals cross the wire as\n * `DecimalString` (string-encoded, no float precision loss); timestamps as\n * nullable `Instant`; JSON payloads as `JsonObject`.\n */\nconst KOTLIN_TYPE_BY_MANIFEST_TYPE: Record<string, string> = {\n id: 'String',\n text: 'String',\n status: 'String',\n foreignKey: 'String',\n crossPackageRef: 'String',\n // 64-bit: SMRT INTEGER columns hold values like byte sizes and epoch\n // millis that overflow a 32-bit Int at decode time.\n integer: 'Long',\n decimal: 'DecimalString?',\n boolean: 'Boolean',\n datetime: 'Instant?',\n json: 'JsonObject',\n};\n\n/**\n * Relationship declarations, not scalar columns — an object row's wire\n * payload does not carry them, so they are excluded from DTO projection.\n */\nconst RELATION_FIELD_TYPES = new Set(['oneToMany', 'manyToMany', 'hasMany']);\n\nconst SWIFT_TYPE_BY_KOTLIN_TYPE: Record<string, string> = {\n String: 'String',\n // Explicit Int64: Swift Int is 32-bit on watchOS arm64_32/armv7k, and the\n // whole point of Long is byte-size/epoch values that overflow 32 bits.\n Long: 'Int64',\n Boolean: 'Bool',\n 'Instant?': 'String?',\n 'DecimalString?': 'String?',\n // Raw JSON string on the Swift side — Swift DTOs are plain structs (no\n // Codable) per the reporter seed; apps parse as needed.\n JsonObject: 'String',\n 'List<String>': '[String]',\n};\n\n/** Name-based fallbacks for manifest fields that carry no `type`. */\nconst BOOLEAN_FIELD_NAMES = new Set([\n 'required',\n 'safetyCritical',\n 'hasSafetyCriticalFallback',\n]);\nconst INTEGER_FIELD_NAMES = new Set([\n 'version',\n 'sortOrder',\n 'precision',\n 'pageStart',\n 'pageEnd',\n 'replayOrder',\n 'clientRevision',\n 'serverRevision',\n]);\nconst STRING_LIST_FIELD_NAMES = new Set([\n 'ruleIds',\n 'systemIds',\n 'supportedLocales',\n 'targetLocales',\n 'requestedLocales',\n 'includedLocales',\n]);\n\nexport function buildMobileContract(\n options: MobileContractOptions,\n): MobileContract {\n const { manifests, allowlist, kotlinPackage, sourceLabel = '' } = options;\n const candidates = manifests.flatMap(listObjects);\n\n const objects: MobileContractObject[] = [];\n const seenQualifiedNames = new Set<string>();\n for (const entry of allowlist) {\n const matches = candidates.filter(\n (object) => object.name === entry || object.qualifiedName === entry,\n );\n if (matches.length === 0) {\n throw new Error(`mobile allowlist references missing object: ${entry}`);\n }\n if (matches.length > 1) {\n const found = matches\n .map((match) => match.qualifiedName ?? match.name)\n .join(', ');\n throw new Error(\n `mobile allowlist entry \"${entry}\" is ambiguous (${found}) — use the qualified name`,\n );\n }\n const qualifiedName = matches[0].qualifiedName ?? matches[0].name;\n if (seenQualifiedNames.has(qualifiedName)) {\n // The same object listed under two spellings (short + qualified) is\n // redundant, not a collision — project it once.\n continue;\n }\n seenQualifiedNames.add(qualifiedName);\n objects.push(toMobileObject(matches[0]));\n }\n\n const dtoNameOwners = new Map<string, string>();\n for (const object of objects) {\n const prior = dtoNameOwners.get(object.dtoName);\n if (prior) {\n throw new Error(\n `mobile allowlist produces duplicate DTO name \"${object.dtoName}\" ` +\n `(${prior} and ${object.qualifiedName}) — generated files would ` +\n 'overwrite each other; allowlisted objects need unique short names',\n );\n }\n dtoNameOwners.set(object.dtoName, object.qualifiedName);\n }\n\n const sourceHash = createHash('sha256')\n .update(JSON.stringify({ allowlist, objects }))\n .digest('hex');\n\n return {\n schemaVersion: SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n contractVersion: SMRT_MOBILE_CONTRACT_VERSION,\n sourceLabel,\n sourceHash,\n objectAllowlist: [...allowlist],\n objectCount: objects.length,\n kotlin: { packageName: kotlinPackage },\n typeMappings: { ...KOTLIN_TYPE_BY_MANIFEST_TYPE },\n objects,\n };\n}\n\nfunction listObjects(manifest: {\n objects: Record<string, SmrtManifestObject> | SmrtManifestObject[];\n}): SmrtManifestObject[] {\n return Array.isArray(manifest.objects)\n ? manifest.objects\n : Object.values(manifest.objects);\n}\n\nfunction toMobileObject(object: SmrtManifestObject): MobileContractObject {\n const fieldEntries = Object.entries(object.fields ?? {});\n const fields: MobileContractField[] = [];\n\n if (!fieldEntries.some(([name]) => name === 'id')) {\n fields.push({\n name: 'id',\n kotlinType: 'String',\n swiftType: 'String',\n nullable: false,\n kotlinDefault: null,\n });\n }\n\n for (const [name, meta] of fieldEntries) {\n if (meta.type && RELATION_FIELD_TYPES.has(meta.type)) {\n continue;\n }\n fields.push(mapField(name, meta));\n }\n\n return {\n name: object.name,\n qualifiedName: object.qualifiedName ?? object.name,\n dtoName: `${object.name}Dto`,\n tableName: object.schema?.tableName ?? null,\n hasTenantId: fieldEntries.some(([name]) => name === 'tenantId'),\n fields,\n };\n}\n\nfunction mapField(name: string, meta: SmrtManifestField): MobileContractField {\n const kotlinType = kotlinTypeFor(name, meta);\n return {\n name,\n kotlinType,\n swiftType: SWIFT_TYPE_BY_KOTLIN_TYPE[kotlinType] ?? 'String',\n nullable: kotlinType.endsWith('?'),\n kotlinDefault: name === 'id' ? null : kotlinDefaultFor(kotlinType, meta),\n };\n}\n\nfunction kotlinTypeFor(name: string, meta: SmrtManifestField): string {\n if (name === 'id') return 'String';\n if (meta.type && KOTLIN_TYPE_BY_MANIFEST_TYPE[meta.type]) {\n return KOTLIN_TYPE_BY_MANIFEST_TYPE[meta.type];\n }\n // Typeless manifest entries: fall back to the name heuristics the amaru\n // seed codegen used.\n if (STRING_LIST_FIELD_NAMES.has(name)) return 'List<String>';\n if (name.endsWith('Json')) return 'JsonObject';\n if (BOOLEAN_FIELD_NAMES.has(name)) return 'Boolean';\n if (INTEGER_FIELD_NAMES.has(name)) return 'Long';\n if (name.endsWith('At') || name.endsWith('On') || name.endsWith('_at'))\n return 'Instant?';\n return 'String';\n}\n\nfunction kotlinDefaultFor(kotlinType: string, meta: SmrtManifestField): string {\n // Current SMRT manifests carry the typed value in `default`; amaru-era\n // manifests carried a (possibly quoted) string in `defaultValue`.\n const declared =\n meta.default !== undefined ? meta.default : meta.defaultValue;\n switch (kotlinType) {\n case 'JsonObject':\n return 'JsonObject(emptyMap())';\n case 'List<String>':\n return 'emptyList()';\n case 'Boolean':\n return declared === true || declared === 'true' ? 'true' : 'false';\n case 'Long':\n return integerDefault(declared);\n case 'Instant?':\n case 'DecimalString?':\n return 'null';\n default:\n return stringDefault(declared);\n }\n}\n\nfunction integerDefault(defaultValue: unknown): string {\n const parsed = Number(String(defaultValue ?? '').replace(/['\"]/g, ''));\n return Number.isFinite(parsed) && String(defaultValue ?? '').trim() !== ''\n ? String(Math.trunc(parsed))\n : '0';\n}\n\nfunction stringDefault(defaultValue: unknown): string {\n if (\n defaultValue === undefined ||\n defaultValue === null ||\n defaultValue === 'null'\n ) {\n return '\"\"';\n }\n\n const text = String(defaultValue).trim();\n if (text === '') {\n return '\"\"';\n }\n if (text.length > 1 && text.startsWith('\"') && text.endsWith('\"')) {\n try {\n const parsed: unknown = JSON.parse(text);\n return typeof parsed === 'string' ? kotlinStringLiteral(parsed) : '\"\"';\n } catch {\n return '\"\"';\n }\n }\n\n const singleQuoted = text.match(/^'([^']*)'$/);\n if (singleQuoted) {\n return kotlinStringLiteral(singleQuoted[1]);\n }\n\n // A raw (unquoted) string value — the shape current SMRT manifests emit.\n return kotlinStringLiteral(text);\n}\n","import {\n SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n SMRT_MOBILE_CONTRACT_VERSION,\n} from './contract-version.js';\n\n/**\n * The generated *framework* contract: the stable files checked into\n * `@happyvertical/smrt-mobile` under\n * `src/commonMain/kotlin/com/happyvertical/smrt/mobile/contract/`.\n * This module is their single source of truth — `pnpm generate:framework`\n * rewrites them; the package build verifies freshness.\n */\n\n// One source for the regenerate command in every emitted header — the script\n// is deliberately named generate:framework (a plain `generate` script would\n// be auto-run by turbo's build task and neutralize the freshness gate).\nconst REGENERATE_COMMAND =\n 'pnpm --filter @happyvertical/smrt-mobile-contract generate:framework';\n\nconst HEADER = [\n '// Generated by @happyvertical/smrt-mobile-contract (framework contract).',\n `// Do not edit by hand — regenerate with \\`${REGENERATE_COMMAND}\\`.`,\n '',\n].join('\\n');\n\nconst SUPPORT_TYPES_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\n\n/**\n * Decimal values cross the wire as strings so no precision is lost between\n * the server's DECIMAL columns and platform floating-point types.\n */\ntypealias DecimalString = String\n\n@Serializable\ndata class Measurement(\n val value: DecimalString,\n val unit: String,\n val source: String? = null,\n)\n`;\n\nconst PACK_LOCALIZATION_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonObject\n\n/**\n * Locale policy for an offline pack: which locale was requested, which are\n * included, and where lookups fall back when a translation is missing.\n */\n@Serializable\ndata class PackLanguage(\n val defaultLocale: String = \"en-US\",\n val requestedLocales: List<String> = emptyList(),\n val includedLocales: List<String> = emptyList(),\n val fallbackLocale: String = \"en-US\",\n val policy: String = \"\",\n val reviewState: String = \"\",\n val hasSafetyCriticalFallback: Boolean = false,\n)\n\n/**\n * Provenance of one localized string: where the text came from and whether the\n * translation is reviewed, safety-critical, or a fallback.\n */\n@Serializable\ndata class PackTextSourceRef(\n val sourceLocale: String = \"\",\n val sourceDocumentId: String = \"\",\n val sourceSectionId: String = \"\",\n val sourceFactId: String = \"\",\n val reviewState: String = \"\",\n val safetyCritical: Boolean = false,\n val fallbackReason: String = \"\",\n)\n\n/**\n * One locale's bundle of localized strings for a pack, keyed by text key,\n * with per-key source provenance.\n */\n@Serializable\ndata class PackLanguageBundle(\n val locale: String = \"\",\n val reviewState: String = \"\",\n val textHash: String = \"\",\n val strings: JsonObject = JsonObject(emptyMap()),\n val sourceRefs: Map<String, PackTextSourceRef> = emptyMap(),\n val hasSafetyCriticalFallback: Boolean = false,\n val policy: String = \"\",\n)\n`;\n\nconst MOBILE_AUTH_CONTRACT_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonObject\n\n/**\n * \\`/api/mobile\\` auth + session contract (server-brokered PKCE).\n *\n * The server owns the OIDC/PKCE exchange: \\`auth/start\\` returns the\n * authorization URL plus the \\`state\\`/\\`codeVerifier\\` the client must persist\n * and echo back on \\`auth/complete\\`. Reference implementation: anytown\n * dashboard; SMRT-shipped handlers are tracked in issue #1748.\n */\n\n@Serializable\ndata class MobileUserSummary(\n val id: String,\n val email: String = \"\",\n val label: String = \"\",\n)\n\n@Serializable\ndata class MobileTenantSummary(\n val id: String,\n val name: String = \"\",\n val slug: String = \"\",\n val planName: String = \"\",\n val subscriptionStatus: String = \"\",\n)\n\n@Serializable\ndata class MobileTenantOption(\n val id: String,\n val name: String = \"\",\n val slug: String = \"\",\n val roleSlug: String = \"\",\n val roleLabel: String = \"\",\n)\n\n@Serializable\ndata class MobileAuthProviderSummary(\n val id: String,\n val label: String = \"\",\n val type: String = \"\",\n val supportsPkce: Boolean = false,\n)\n\n@Serializable\ndata class MobileAuthStartRequest(\n val providerId: String? = null,\n val redirectUri: String,\n val scopes: List<String> = emptyList(),\n val state: String? = null,\n val loginHint: String? = null,\n)\n\n@Serializable\ndata class MobileAuthStartResponse(\n val providerId: String,\n val authorizationUrl: String,\n val state: String,\n val codeVerifier: String? = null,\n val nonce: String? = null,\n val redirectUri: String,\n)\n\n@Serializable\ndata class MobileAuthCompleteRequest(\n val providerId: String? = null,\n val code: String,\n val state: String? = null,\n val codeVerifier: String? = null,\n val redirectUri: String,\n)\n\n@Serializable\ndata class MobileAuthSession(\n val accessToken: String,\n val tokenType: String = \"Bearer\",\n val expiresAt: String = \"\",\n val user: MobileUserSummary,\n val activeTenant: MobileTenantOption? = null,\n val tenants: List<MobileTenantOption> = emptyList(),\n)\n\n@Serializable\ndata class MobileSessionBootstrap(\n val user: MobileUserSummary,\n val activeTenant: MobileTenantOption? = null,\n val tenants: List<MobileTenantOption> = emptyList(),\n val extras: JsonObject? = null,\n)\n`;\n\nconst MOBILE_DEVICE_CONTRACT_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class MobileDevicePermissionState(\n val status: String,\n val canRequest: Boolean = false,\n val reason: String? = null,\n)\n\n@Serializable\ndata class MobileDeviceCapability(\n val surface: String,\n val label: String = \"\",\n val supported: Boolean = false,\n val permission: MobileDevicePermissionState,\n val preferredInput: String = \"\",\n)\n\n@Serializable\ndata class MobileDeviceCapabilities(\n val camera: MobileDeviceCapability,\n val microphone: MobileDeviceCapability,\n val checkedAtEpochMillis: Long? = null,\n)\n`;\n\nconst MOBILE_CONTRACT_INFO_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nconst val SMRT_MOBILE_CONTRACT_SCHEMA_VERSION: Int = ${SMRT_MOBILE_CONTRACT_SCHEMA_VERSION}\nconst val SMRT_MOBILE_CONTRACT_VERSION: String = \"${SMRT_MOBILE_CONTRACT_VERSION}\"\n`;\n\n/** Kotlin framework contract files, keyed by file name. */\nexport function frameworkKotlinFiles(): Map<string, string> {\n return new Map([\n ['SupportTypes.kt', SUPPORT_TYPES_KT],\n ['PackLocalization.kt', PACK_LOCALIZATION_KT],\n ['MobileAuthContract.kt', MOBILE_AUTH_CONTRACT_KT],\n ['MobileDeviceContract.kt', MOBILE_DEVICE_CONTRACT_KT],\n ['MobileContractInfo.kt', MOBILE_CONTRACT_INFO_KT],\n ]);\n}\n\nconst MOBILE_CONTRACT_SWIFT = `// Generated by @happyvertical/smrt-mobile-contract (framework contract).\n// Do not edit by hand — regenerate with \\`${REGENERATE_COMMAND}\\`.\nimport Foundation\n\nlet SmrtMobileContractSchemaVersion = ${SMRT_MOBILE_CONTRACT_SCHEMA_VERSION}\nlet SmrtMobileContractVersion = \"${SMRT_MOBILE_CONTRACT_VERSION}\"\n\n/// Decimal values cross the wire as strings (no precision loss).\ntypealias DecimalString = String\n\nstruct Measurement {\n let value: DecimalString\n let unit: String\n let source: String?\n}\n\nstruct MobileUserSummary {\n let id: String\n let email: String\n let label: String\n}\n\nstruct MobileTenantSummary {\n let id: String\n let name: String\n let slug: String\n let planName: String\n let subscriptionStatus: String\n}\n\nstruct MobileTenantOption {\n let id: String\n let name: String\n let slug: String\n let roleSlug: String\n let roleLabel: String\n}\n\nstruct MobileAuthProviderSummary {\n let id: String\n let label: String\n let type: String\n let supportsPkce: Bool\n}\n\nstruct MobileAuthStartRequest {\n let providerId: String?\n let redirectUri: String\n let scopes: [String]\n let state: String?\n let loginHint: String?\n}\n\nstruct MobileAuthStartResponse {\n let providerId: String\n let authorizationUrl: String\n let state: String\n let codeVerifier: String?\n let nonce: String?\n let redirectUri: String\n}\n\nstruct MobileAuthCompleteRequest {\n let providerId: String?\n let code: String\n let state: String?\n let codeVerifier: String?\n let redirectUri: String\n}\n\nstruct MobileAuthSession {\n let accessToken: String\n let tokenType: String\n let expiresAt: String\n let user: MobileUserSummary\n let activeTenant: MobileTenantOption?\n let tenants: [MobileTenantOption]\n}\n\nstruct MobileSessionBootstrap {\n let user: MobileUserSummary\n let activeTenant: MobileTenantOption?\n let tenants: [MobileTenantOption]\n let extras: String?\n}\n\nstruct MobileDevicePermissionState {\n let status: String\n let canRequest: Bool\n let reason: String?\n}\n\nstruct MobileDeviceCapability {\n let surface: String\n let label: String\n let supported: Bool\n let permission: MobileDevicePermissionState\n let preferredInput: String\n}\n\nstruct MobileDeviceCapabilities {\n let camera: MobileDeviceCapability\n let microphone: MobileDeviceCapability\n let checkedAtEpochMillis: Int64?\n}\n`;\n\n/**\n * Swift mirror of the framework contract for SwiftUI apps (reporter\n * precedent). Not checked into smrt-mobile — iOS wiring lands with\n * smrt-ios (Phase 6); apps can emit it via this API meanwhile.\n */\nexport function frameworkSwiftFiles(): Map<string, string> {\n return new Map([['MobileContract.swift', MOBILE_CONTRACT_SWIFT]]);\n}\n","import { kotlinIdentifier } from './identifiers.js';\nimport type {\n MobileContract,\n MobileContractField,\n MobileContractObject,\n} from './types.js';\n\nexport const GENERATED_HEADER = [\n '// Generated by @happyvertical/smrt-mobile-contract.',\n '// Do not edit by hand.',\n '',\n].join('\\n');\n\nconst FRAMEWORK_CONTRACT_PACKAGE = 'com.happyvertical.smrt.mobile.contract';\n\n/**\n * Emits one Kotlin DTO file per allowlisted object, plus a\n * `MobileContractManifest.kt` recording the contract identity, all in the\n * consumer's Kotlin package. Framework support types (DecimalString, …) are\n * imported from `com.happyvertical.smrt.mobile.contract`.\n */\nexport function generateKotlinDtoFiles(\n contract: MobileContract,\n): Map<string, string> {\n const files = new Map<string, string>();\n\n files.set('MobileContractManifest.kt', manifestFile(contract));\n for (const object of contract.objects) {\n files.set(`${object.dtoName}.kt`, dtoFile(contract, object));\n }\n\n return files;\n}\n\nfunction manifestFile(contract: MobileContract): string {\n return [\n GENERATED_HEADER,\n `package ${contract.kotlin.packageName}`,\n '',\n `const val MOBILE_CONTRACT_SCHEMA_VERSION: Int = ${contract.schemaVersion}`,\n `const val MOBILE_CONTRACT_VERSION: String = \"${contract.contractVersion}\"`,\n `const val MOBILE_CONTRACT_SOURCE_HASH: String = \"${contract.sourceHash}\"`,\n '',\n 'val MOBILE_CONTRACT_OBJECTS: List<String> = listOf(',\n ...contract.objects.map((object) => ` \"${object.name}\",`),\n ')',\n '',\n ].join('\\n');\n}\n\nfunction dtoFile(\n contract: MobileContract,\n object: MobileContractObject,\n): string {\n const imports = new Set(['kotlinx.serialization.Serializable']);\n for (const field of object.fields) {\n if (field.kotlinType.includes('Instant')) {\n imports.add('kotlinx.datetime.Instant');\n }\n if (field.kotlinType.includes('JsonObject')) {\n imports.add('kotlinx.serialization.json.JsonObject');\n }\n if (field.kotlinType.includes('DecimalString')) {\n imports.add(`${FRAMEWORK_CONTRACT_PACKAGE}.DecimalString`);\n }\n }\n\n return [\n GENERATED_HEADER,\n `package ${contract.kotlin.packageName}`,\n '',\n ...[...imports].sort().map((importPath) => `import ${importPath}`),\n '',\n '@Serializable',\n `data class ${object.dtoName}(`,\n ...object.fields.map((field) => kotlinProperty(field)),\n ')',\n '',\n ].join('\\n');\n}\n\nfunction kotlinProperty(field: MobileContractField): string {\n const defaultValue =\n field.kotlinDefault === null ? '' : ` = ${field.kotlinDefault}`;\n return ` val ${kotlinIdentifier(field.name)}: ${field.kotlinType}${defaultValue},`;\n}\n","import { swiftIdentifier } from './identifiers.js';\nimport type { MobileContract, MobileContractField } from './types.js';\n\n/**\n * Emits all allowlisted domain DTOs into a single Swift file. Structs are\n * plain (no Codable) per the reporter seed — apps decode JSON as needed.\n * Kotlin `JsonObject` fields arrive as raw JSON strings on the Swift side.\n */\nexport function generateSwiftDtoFile(contract: MobileContract): string {\n const lines: string[] = [\n '// Generated by @happyvertical/smrt-mobile-contract.',\n '// Do not edit by hand.',\n 'import Foundation',\n '',\n `let MobileContractSchemaVersion = ${contract.schemaVersion}`,\n `let MobileContractVersion = \"${contract.contractVersion}\"`,\n `let MobileContractSourceHash = \"${contract.sourceHash}\"`,\n '',\n ];\n\n for (const object of contract.objects) {\n lines.push(`struct ${object.dtoName} {`);\n for (const field of object.fields) {\n lines.push(` let ${swiftIdentifier(field.name)}: ${swiftType(field)}`);\n }\n lines.push('}', '');\n }\n\n return lines.join('\\n');\n}\n\nfunction swiftType(field: MobileContractField): string {\n return field.swiftType;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/** Writes every `relPath → content` entry under `rootDir`. */\nexport function writeFileSet(\n rootDir: string,\n files: Map<string, string>,\n): void {\n for (const [relPath, content] of files) {\n const absPath = join(rootDir, relPath);\n mkdirSync(dirname(absPath), { recursive: true });\n writeFileSync(absPath, content);\n }\n}\n\n/**\n * Compares every entry against disk. Returns the list of missing/stale\n * relative paths (empty = fresh).\n */\nexport function verifyFileSet(\n rootDir: string,\n files: Map<string, string>,\n): string[] {\n const stale: string[] = [];\n for (const [relPath, content] of files) {\n const absPath = join(rootDir, relPath);\n if (!existsSync(absPath) || readFileSync(absPath, 'utf8') !== content) {\n stale.push(relPath);\n }\n }\n return stale;\n}\n","/**\n * TypeScript wire types for the `/api/mobile` auth + session contract.\n *\n * These are the SAME shapes the framework Kotlin contract ships to mobile\n * clients (`MOBILE_AUTH_CONTRACT_KT` in `emit-framework.ts`, checked into\n * `@happyvertical/smrt-mobile` as `MobileAuthContract.kt`). Server-side\n * implementations — the reusable SvelteKit handlers in\n * `@happyvertical/smrt-users` (issue #1748) — import them from here so the\n * wire contract has one owning package on both sides.\n *\n * Two sync guarantees keep the three representations locked together:\n *\n * 1. Each interface has a `MobileWireShape` descriptor below. The `satisfies`\n * check makes the descriptor fail to COMPILE when it disagrees with the\n * interface (missing/extra field, or wrong required/optional/nullable\n * kind).\n * 2. `__tests__/framework-auth-types.test.ts` parses the Kotlin literal and\n * asserts it matches the descriptors field-for-field, so editing either\n * side without the other fails the contract package's tests.\n *\n * Kotlin → TypeScript field mapping:\n * - `val x: T` (no default) → `x: T` (required)\n * - `val x: T = <non-null value>` → `x?: T` (kotlinx.serialization omits\n * default-equal values by default, and decoders fill absent fields from\n * defaults — so the wire may omit them in either direction)\n * - `val x: T? = null` → `x?: T | null`\n *\n * @packageDocumentation\n */\n\n/**\n * Classifies one wire field. Mirrors the three Kotlin declaration forms the\n * framework contract uses (see the mapping table in the module docs).\n */\nexport type MobileWireFieldKind = 'required' | 'optional' | 'nullable';\n\n/**\n * Compile-time-checked shape descriptor for a wire type: one entry per field,\n * whose kind is DERIVED from the TypeScript declaration. Using\n * `Extract<keyof T, string>` keeps the mapped type non-homomorphic, so every\n * key must be listed (optionality is not copied from `T`) while `T[K]` still\n * carries `undefined` for optional properties.\n */\nexport type MobileWireShape<T> = {\n [K in Extract<keyof T, string>]: undefined extends T[K]\n ? null extends T[K]\n ? 'nullable'\n : 'optional'\n : 'required';\n};\n\n/** Signed-in user summary returned by auth/complete and the session bootstrap. */\nexport interface MobileUserSummary {\n id: string;\n email?: string;\n label?: string;\n}\n\n/** Tenant summary with subscription surface (reserved for app responses). */\nexport interface MobileTenantSummary {\n id: string;\n name?: string;\n slug?: string;\n planName?: string;\n subscriptionStatus?: string;\n}\n\n/** One selectable tenant, labeled with the role the user holds there. */\nexport interface MobileTenantOption {\n id: string;\n name?: string;\n slug?: string;\n roleSlug?: string;\n roleLabel?: string;\n}\n\n/** One configured auth provider (reserved for app-side provider pickers). */\nexport interface MobileAuthProviderSummary {\n id: string;\n label?: string;\n type?: string;\n supportsPkce?: boolean;\n}\n\n/** Body of `POST /api/mobile/auth/start`. */\nexport interface MobileAuthStartRequest {\n providerId?: string | null;\n redirectUri: string;\n scopes?: string[];\n state?: string | null;\n loginHint?: string | null;\n}\n\n/**\n * Response of `POST /api/mobile/auth/start`. The client persists `state` and\n * `codeVerifier` (as an opaque pending handshake) and echoes them back on\n * `auth/complete`; `state` is also validated against the IdP redirect.\n */\nexport interface MobileAuthStartResponse {\n providerId: string;\n authorizationUrl: string;\n state: string;\n codeVerifier?: string | null;\n nonce?: string | null;\n redirectUri: string;\n}\n\n/** Body of `POST /api/mobile/auth/complete`. */\nexport interface MobileAuthCompleteRequest {\n providerId?: string | null;\n code: string;\n state?: string | null;\n codeVerifier?: string | null;\n redirectUri: string;\n}\n\n/**\n * Response of `POST /api/mobile/auth/complete` — the mobile bearer session.\n * `accessToken` goes into `Authorization: Bearer <token>` on every\n * authenticated `/api/mobile` request.\n */\nexport interface MobileAuthSession {\n accessToken: string;\n tokenType?: string;\n expiresAt?: string;\n user: MobileUserSummary;\n activeTenant?: MobileTenantOption | null;\n tenants?: MobileTenantOption[];\n}\n\n/**\n * Response of `GET /api/mobile/session` — the app-boot payload for a stored\n * bearer. `extras` is an app-defined JSON object escape hatch (must remain a\n * JSON object; the Kotlin side decodes it as `JsonObject`).\n */\nexport interface MobileSessionBootstrap {\n user: MobileUserSummary;\n activeTenant?: MobileTenantOption | null;\n tenants?: MobileTenantOption[];\n extras?: Record<string, unknown> | null;\n}\n\nexport const MOBILE_USER_SUMMARY_SHAPE = {\n id: 'required',\n email: 'optional',\n label: 'optional',\n} as const satisfies MobileWireShape<MobileUserSummary>;\n\nexport const MOBILE_TENANT_SUMMARY_SHAPE = {\n id: 'required',\n name: 'optional',\n slug: 'optional',\n planName: 'optional',\n subscriptionStatus: 'optional',\n} as const satisfies MobileWireShape<MobileTenantSummary>;\n\nexport const MOBILE_TENANT_OPTION_SHAPE = {\n id: 'required',\n name: 'optional',\n slug: 'optional',\n roleSlug: 'optional',\n roleLabel: 'optional',\n} as const satisfies MobileWireShape<MobileTenantOption>;\n\nexport const MOBILE_AUTH_PROVIDER_SUMMARY_SHAPE = {\n id: 'required',\n label: 'optional',\n type: 'optional',\n supportsPkce: 'optional',\n} as const satisfies MobileWireShape<MobileAuthProviderSummary>;\n\nexport const MOBILE_AUTH_START_REQUEST_SHAPE = {\n providerId: 'nullable',\n redirectUri: 'required',\n scopes: 'optional',\n state: 'nullable',\n loginHint: 'nullable',\n} as const satisfies MobileWireShape<MobileAuthStartRequest>;\n\nexport const MOBILE_AUTH_START_RESPONSE_SHAPE = {\n providerId: 'required',\n authorizationUrl: 'required',\n state: 'required',\n codeVerifier: 'nullable',\n nonce: 'nullable',\n redirectUri: 'required',\n} as const satisfies MobileWireShape<MobileAuthStartResponse>;\n\nexport const MOBILE_AUTH_COMPLETE_REQUEST_SHAPE = {\n providerId: 'nullable',\n code: 'required',\n state: 'nullable',\n codeVerifier: 'nullable',\n redirectUri: 'required',\n} as const satisfies MobileWireShape<MobileAuthCompleteRequest>;\n\nexport const MOBILE_AUTH_SESSION_SHAPE = {\n accessToken: 'required',\n tokenType: 'optional',\n expiresAt: 'optional',\n user: 'required',\n activeTenant: 'nullable',\n tenants: 'optional',\n} as const satisfies MobileWireShape<MobileAuthSession>;\n\nexport const MOBILE_SESSION_BOOTSTRAP_SHAPE = {\n user: 'required',\n activeTenant: 'nullable',\n tenants: 'optional',\n extras: 'nullable',\n} as const satisfies MobileWireShape<MobileSessionBootstrap>;\n\n/**\n * All auth-contract shape descriptors, keyed by the Kotlin data class name.\n * The parity test asserts this map covers exactly the data classes declared\n * in `MobileAuthContract.kt` — adding a class to either side without the\n * other fails the suite.\n */\nexport const MOBILE_AUTH_WIRE_SHAPES: Record<\n string,\n Record<string, MobileWireFieldKind>\n> = {\n MobileUserSummary: MOBILE_USER_SUMMARY_SHAPE,\n MobileTenantSummary: MOBILE_TENANT_SUMMARY_SHAPE,\n MobileTenantOption: MOBILE_TENANT_OPTION_SHAPE,\n MobileAuthProviderSummary: MOBILE_AUTH_PROVIDER_SUMMARY_SHAPE,\n MobileAuthStartRequest: MOBILE_AUTH_START_REQUEST_SHAPE,\n MobileAuthStartResponse: MOBILE_AUTH_START_RESPONSE_SHAPE,\n MobileAuthCompleteRequest: MOBILE_AUTH_COMPLETE_REQUEST_SHAPE,\n MobileAuthSession: MOBILE_AUTH_SESSION_SHAPE,\n MobileSessionBootstrap: MOBILE_SESSION_BOOTSTRAP_SHAPE,\n};\n"],"mappings":";;;;AACO,IAAM,sCAAsC;AAM5C,IAAM,+BAA+B;;;ACA5C,IAAM,uCAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,iCAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,mBAAmB;AAGlB,SAAS,iBAAiB,MAAsB;CACrD,OAAO,qBAAqB,IAAI,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,IAChE,KAAK,KAAI,MACT;AACN;AAGO,SAAS,gBAAgB,MAAsB;CACpD,OAAO,eAAe,IAAI,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,IAC1D,KAAK,KAAI,MACT;AACN;AAOO,SAAS,oBAAoB,OAAuB;CACzD,IAAI,MAAM;CACV,KAAA,MAAW,QAAQ,OACjB,QAAQ,MAAR;EACE,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,SAAS;GACP,MAAM,OAAO,KAAK,YAAY,CAAC,KAAK;GAIpC,OACE,OAAO,MAAS,QAAQ,SAAU,QAAQ,QACtC,MAAM,KAAK,SAAS,EAAE,CAAA,CAAE,SAAS,GAAG,GAAG,MACvC;EACR;CACF;CAEF,OAAO,GAAG,IAAG;AACf;;;AChIA,IAAM,+BAAuD;CAC3D,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,YAAY;CACZ,iBAAiB;CAGjB,SAAS;CACT,SAAS;CACT,SAAS;CACT,UAAU;CACV,MAAM;AACR;AAMA,IAAM,uCAAuB,IAAI,IAAI;CAAC;CAAa;CAAc;AAAS,CAAC;AAE3E,IAAM,4BAAoD;CACxD,QAAQ;CAGR,MAAM;CACN,SAAS;CACT,YAAY;CACZ,kBAAkB;CAGlB,YAAY;CACZ,gBAAgB;AAClB;AAGA,IAAM,sCAAsB,IAAI,IAAI;CAClC;CACA;CACA;AACF,CAAC;AACD,IAAM,sCAAsB,IAAI,IAAI;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,IAAM,0CAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAEM,SAAS,oBACd,SACgB;CAChB,MAAM,EAAE,WAAW,WAAW,eAAe,cAAc,OAAO;CAClE,MAAM,aAAa,UAAU,QAAQ,WAAW;CAEhD,MAAM,UAAkC,CAAC;CACzC,MAAM,qCAAqB,IAAI,IAAY;CAC3C,KAAA,MAAW,SAAS,WAAW;EAC7B,MAAM,UAAU,WAAW,QACxB,WAAW,OAAO,SAAS,SAAS,OAAO,kBAAkB,KAChE;EACA,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MAAM,+CAA+C,OAAO;EAExE,IAAI,QAAQ,SAAS,GAAG;GACtB,MAAM,QAAQ,QACX,KAAK,UAAU,MAAM,iBAAiB,MAAM,IAAI,CAAA,CAChD,KAAK,IAAI;GACZ,MAAM,IAAI,MACR,2BAA2B,MAAK,kBAAmB,MAAK,gCAC1D;EACF;EACA,MAAM,gBAAgB,QAAQ,EAAC,CAAE,iBAAiB,QAAQ,EAAC,CAAE;EAC7D,IAAI,mBAAmB,IAAI,aAAa,GAGtC;EAEF,mBAAmB,IAAI,aAAa;EACpC,QAAQ,KAAK,eAAe,QAAQ,EAAE,CAAC;CACzC;CAEA,MAAM,gCAAgB,IAAI,IAAoB;CAC9C,KAAA,MAAW,UAAU,SAAS;EAC5B,MAAM,QAAQ,cAAc,IAAI,OAAO,OAAO;EAC9C,IAAI,OACF,MAAM,IAAI,MACR,iDAAiD,OAAO,QAAO,KACzD,MAAK,OAAQ,OAAO,cAAa,iGAEzC;EAEF,cAAc,IAAI,OAAO,SAAS,OAAO,aAAa;CACxD;CAMA,OAAO;EACL,eAAA;EACA,iBAAiB;EACjB;EACA,YARiB,WAAW,QAAQ,CAAA,CACnC,OAAO,KAAK,UAAU;GAAE;GAAW;EAAQ,CAAC,CAAC,CAAA,CAC7C,OAAO,KAMR;EACA,iBAAiB,CAAC,GAAG,SAAS;EAC9B,aAAa,QAAQ;EACrB,QAAQ,EAAE,aAAa,cAAc;EACrC,cAAc,EAAE,GAAG,6BAA6B;EAChD;CACF;AACF;AAEA,SAAS,YAAY,UAEI;CACvB,OAAO,MAAM,QAAQ,SAAS,OAAO,IACjC,SAAS,UACT,OAAO,OAAO,SAAS,OAAO;AACpC;AAEA,SAAS,eAAe,QAAkD;CACxE,MAAM,eAAe,OAAO,QAAQ,OAAO,UAAU,CAAC,CAAC;CACvD,MAAM,SAAgC,CAAC;CAEvC,IAAI,CAAC,aAAa,MAAM,CAAC,UAAU,SAAS,IAAI,GAC9C,OAAO,KAAK;EACV,MAAM;EACN,YAAY;EACZ,WAAW;EACX,UAAU;EACV,eAAe;CACjB,CAAC;CAGH,KAAA,MAAW,CAAC,MAAM,SAAS,cAAc;EACvC,IAAI,KAAK,QAAQ,qBAAqB,IAAI,KAAK,IAAI,GACjD;EAEF,OAAO,KAAK,SAAS,MAAM,IAAI,CAAC;CAClC;CAEA,OAAO;EACL,MAAM,OAAO;EACb,eAAe,OAAO,iBAAiB,OAAO;EAC9C,SAAS,GAAG,OAAO,KAAI;EACvB,WAAW,OAAO,QAAQ,aAAa;EACvC,aAAa,aAAa,MAAM,CAAC,UAAU,SAAS,UAAU;EAC9D;CACF;AACF;AAEA,SAAS,SAAS,MAAc,MAA8C;CAC5E,MAAM,aAAa,cAAc,MAAM,IAAI;CAC3C,OAAO;EACL;EACA;EACA,WAAW,0BAA0B,eAAe;EACpD,UAAU,WAAW,SAAS,GAAG;EACjC,eAAe,SAAS,OAAO,OAAO,iBAAiB,YAAY,IAAI;CACzE;AACF;AAEA,SAAS,cAAc,MAAc,MAAiC;CACpE,IAAI,SAAS,MAAM,OAAO;CAC1B,IAAI,KAAK,QAAQ,6BAA6B,KAAK,OACjD,OAAO,6BAA6B,KAAK;CAI3C,IAAI,wBAAwB,IAAI,IAAI,GAAG,OAAO;CAC9C,IAAI,KAAK,SAAS,MAAM,GAAG,OAAO;CAClC,IAAI,oBAAoB,IAAI,IAAI,GAAG,OAAO;CAC1C,IAAI,oBAAoB,IAAI,IAAI,GAAG,OAAO;CAC1C,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,KAAK,GACnE,OAAO;CACT,OAAO;AACT;AAEA,SAAS,iBAAiB,YAAoB,MAAiC;CAG7E,MAAM,WACJ,KAAK,YAAY,KAAA,IAAY,KAAK,UAAU,KAAK;CACnD,QAAQ,YAAR;EACE,KAAK,cACH,OAAO;EACT,KAAK,gBACH,OAAO;EACT,KAAK,WACH,OAAO,aAAa,QAAQ,aAAa,SAAS,SAAS;EAC7D,KAAK,QACH,OAAO,eAAe,QAAQ;EAChC,KAAK;EACL,KAAK,kBACH,OAAO;EACT,SACE,OAAO,cAAc,QAAQ;CACjC;AACF;AAEA,SAAS,eAAe,cAA+B;CACrD,MAAM,SAAS,OAAO,OAAO,gBAAgB,EAAE,CAAA,CAAE,QAAQ,SAAS,EAAE,CAAC;CACrE,OAAO,OAAO,SAAS,MAAM,KAAK,OAAO,gBAAgB,EAAE,CAAA,CAAE,KAAK,MAAM,KACpE,OAAO,KAAK,MAAM,MAAM,CAAC,IACzB;AACN;AAEA,SAAS,cAAc,cAA+B;CACpD,IACE,iBAAiB,KAAA,KACjB,iBAAiB,QACjB,iBAAiB,QAEjB,OAAO;CAGT,MAAM,OAAO,OAAO,YAAY,CAAA,CAAE,KAAK;CACvC,IAAI,SAAS,IACX,OAAO;CAET,IAAI,KAAK,SAAS,KAAK,KAAK,WAAW,IAAG,KAAK,KAAK,SAAS,IAAG,GAC9D,IAAI;EACF,MAAM,SAAkB,KAAK,MAAM,IAAI;EACvC,OAAO,OAAO,WAAW,WAAW,oBAAoB,MAAM,IAAI;CACpE,QAAQ;EACN,OAAO;CACT;CAGF,MAAM,eAAe,KAAK,MAAM,aAAa;CAC7C,IAAI,cACF,OAAO,oBAAoB,aAAa,EAAE;CAI5C,OAAO,oBAAoB,IAAI;AACjC;;;AC3PA,IAAM,qBACJ;AAEF,IAAM,SAAS;CACb;CACA,mDAA8C,mBAAkB;CAChE;AACF,CAAA,CAAE,KAAK,IAAI;AAEX,IAAM,mBAAmB,GAAG,OAAM;;;;;;;;;;;;;;;;;;AAmBlC,IAAM,uBAAuB,GAAG,OAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoDtC,IAAM,0BAA0B,GAAG,OAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+FzC,IAAM,4BAA4B,GAAG,OAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6B3C,IAAM,0BAA0B,GAAG,OAAM;;;;oDAIW,6BAA4B;;AAIzE,SAAS,uBAA4C;CAC1D,uBAAO,IAAI,IAAI;EACb,CAAC,mBAAmB,gBAAgB;EACpC,CAAC,uBAAuB,oBAAoB;EAC5C,CAAC,yBAAyB,uBAAuB;EACjD,CAAC,2BAA2B,yBAAyB;EACrD,CAAC,yBAAyB,uBAAuB;CACnD,CAAC;AACH;AAEA,IAAM,wBAAwB;kDACe,mBAAkB;;;;mCAI5B,6BAA4B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2GxD,SAAS,sBAA2C;CACzD,uBAAO,IAAI,IAAI,CAAC,CAAC,wBAAwB,qBAAqB,CAAC,CAAC;AAClE;;;ACzVO,IAAM,mBAAmB;CAC9B;CACA;CACA;AACF,CAAA,CAAE,KAAK,IAAI;AAEX,IAAM,6BAA6B;AAQ5B,SAAS,uBACd,UACqB;CACrB,MAAM,wBAAQ,IAAI,IAAoB;CAEtC,MAAM,IAAI,6BAA6B,aAAa,QAAQ,CAAC;CAC7D,KAAA,MAAW,UAAU,SAAS,SAC5B,MAAM,IAAI,GAAG,OAAO,QAAO,MAAO,QAAQ,UAAU,MAAM,CAAC;CAG7D,OAAO;AACT;AAEA,SAAS,aAAa,UAAkC;CACtD,OAAO;EACL;EACA,WAAW,SAAS,OAAO;EAC3B;EACA,mDAAmD,SAAS;EAC5D,gDAAgD,SAAS,gBAAe;EACxE,oDAAoD,SAAS,WAAU;EACvE;EACA;EACA,GAAG,SAAS,QAAQ,KAAK,WAAW,QAAQ,OAAO,KAAI,GAAI;EAC3D;EACA;CACF,CAAA,CAAE,KAAK,IAAI;AACb;AAEA,SAAS,QACP,UACA,QACQ;CACR,MAAM,0BAAU,IAAI,IAAI,CAAC,oCAAoC,CAAC;CAC9D,KAAA,MAAW,SAAS,OAAO,QAAQ;EACjC,IAAI,MAAM,WAAW,SAAS,SAAS,GACrC,QAAQ,IAAI,0BAA0B;EAExC,IAAI,MAAM,WAAW,SAAS,YAAY,GACxC,QAAQ,IAAI,uCAAuC;EAErD,IAAI,MAAM,WAAW,SAAS,eAAe,GAC3C,QAAQ,IAAI,GAAG,2BAA0B,eAAgB;CAE7D;CAEA,OAAO;EACL;EACA,WAAW,SAAS,OAAO;EAC3B;EACA,GAAG,CAAC,GAAG,OAAO,CAAA,CAAE,KAAK,CAAA,CAAE,KAAK,eAAe,UAAU,YAAY;EACjE;EACA;EACA,cAAc,OAAO,QAAO;EAC5B,GAAG,OAAO,OAAO,KAAK,UAAU,eAAe,KAAK,CAAC;EACrD;EACA;CACF,CAAA,CAAE,KAAK,IAAI;AACb;AAEA,SAAS,eAAe,OAAoC;CAC1D,MAAM,eACJ,MAAM,kBAAkB,OAAO,KAAK,MAAM,MAAM;CAClD,OAAO,WAAW,iBAAiB,MAAM,IAAI,EAAC,IAAK,MAAM,aAAa,aAAY;AACpF;;;AC7EO,SAAS,qBAAqB,UAAkC;CACrE,MAAM,QAAkB;EACtB;EACA;EACA;EACA;EACA,qCAAqC,SAAS;EAC9C,gCAAgC,SAAS,gBAAe;EACxD,mCAAmC,SAAS,WAAU;EACtD;CACF;CAEA,KAAA,MAAW,UAAU,SAAS,SAAS;EACrC,MAAM,KAAK,UAAU,OAAO,QAAO,GAAI;EACvC,KAAA,MAAW,SAAS,OAAO,QACzB,MAAM,KAAK,SAAS,gBAAgB,MAAM,IAAI,EAAC,IAAK,UAAU,KAAK,GAAG;EAExE,MAAM,KAAK,KAAK,EAAE;CACpB;CAEA,OAAO,MAAM,KAAK,IAAI;AACxB;AAEA,SAAS,UAAU,OAAoC;CACrD,OAAO,MAAM;AACf;;;AC7BO,SAAS,aACd,SACA,OACM;CACN,KAAA,MAAW,CAAC,SAAS,YAAY,OAAO;EACtC,MAAM,UAAU,KAAK,SAAS,OAAO;EACrC,UAAU,QAAQ,OAAO,GAAG,EAAE,WAAW,KAAK,CAAC;EAC/C,cAAc,SAAS,OAAO;CAChC;AACF;AAMO,SAAS,cACd,SACA,OACU;CACV,MAAM,QAAkB,CAAC;CACzB,KAAA,MAAW,CAAC,SAAS,YAAY,OAAO;EACtC,MAAM,UAAU,KAAK,SAAS,OAAO;EACrC,IAAI,CAAC,WAAW,OAAO,KAAK,aAAa,SAAS,MAAM,MAAM,SAC5D,MAAM,KAAK,OAAO;CAEtB;CACA,OAAO;AACT;;;AC+GO,IAAM,4BAA4B;CACvC,IAAI;CACJ,OAAO;CACP,OAAO;AACT;AAEO,IAAM,8BAA8B;CACzC,IAAI;CACJ,MAAM;CACN,MAAM;CACN,UAAU;CACV,oBAAoB;AACtB;AAEO,IAAM,6BAA6B;CACxC,IAAI;CACJ,MAAM;CACN,MAAM;CACN,UAAU;CACV,WAAW;AACb;AAEO,IAAM,qCAAqC;CAChD,IAAI;CACJ,OAAO;CACP,MAAM;CACN,cAAc;AAChB;AAEO,IAAM,kCAAkC;CAC7C,YAAY;CACZ,aAAa;CACb,QAAQ;CACR,OAAO;CACP,WAAW;AACb;AAEO,IAAM,mCAAmC;CAC9C,YAAY;CACZ,kBAAkB;CAClB,OAAO;CACP,cAAc;CACd,OAAO;CACP,aAAa;AACf;AAEO,IAAM,qCAAqC;CAChD,YAAY;CACZ,MAAM;CACN,OAAO;CACP,cAAc;CACd,aAAa;AACf;AAEO,IAAM,4BAA4B;CACvC,aAAa;CACb,WAAW;CACX,WAAW;CACX,MAAM;CACN,cAAc;CACd,SAAS;AACX;AAEO,IAAM,iCAAiC;CAC5C,MAAM;CACN,cAAc;CACd,SAAS;CACT,QAAQ;AACV;AAQO,IAAM,0BAGT;CACF,mBAAmB;CACnB,qBAAqB;CACrB,oBAAoB;CACpB,2BAA2B;CAC3B,wBAAwB;CACxB,yBAAyB;CACzB,2BAA2B;CAC3B,mBAAmB;CACnB,wBAAwB;AAC1B"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/contract-version.ts","../src/identifiers.ts","../src/build-contract.ts","../src/emit-framework.ts","../src/emit-kotlin.ts","../src/emit-swift.ts","../src/file-set.ts","../src/framework-types.ts"],"sourcesContent":["/** Schema version of the mobile-contract.json envelope. */\nexport const SMRT_MOBILE_CONTRACT_SCHEMA_VERSION = 1;\n\n/**\n * Version stamp of the framework contract surface (auth/session/device/pack\n * localization types). Bump when the framework contract shape changes.\n */\nexport const SMRT_MOBILE_CONTRACT_VERSION = '2026-07-01.v1';\n","/**\n * Identifier + literal escaping for generated Kotlin/Swift source. Manifest\n * field names are valid JS identifiers, but may collide with Kotlin/Swift\n * keywords; string defaults may contain characters that Kotlin string\n * templates or escapes treat specially.\n */\n\nconst KOTLIN_HARD_KEYWORDS = new Set([\n 'as',\n 'break',\n 'class',\n 'continue',\n 'do',\n 'else',\n 'false',\n 'for',\n 'fun',\n 'if',\n 'in',\n 'interface',\n 'is',\n 'null',\n 'object',\n 'package',\n 'return',\n 'super',\n 'this',\n 'throw',\n 'true',\n 'try',\n 'typealias',\n 'typeof',\n 'val',\n 'var',\n 'when',\n 'while',\n]);\n\nconst SWIFT_KEYWORDS = new Set([\n 'associatedtype',\n 'as',\n 'break',\n 'case',\n 'catch',\n 'class',\n 'continue',\n 'default',\n 'defer',\n 'deinit',\n 'do',\n 'else',\n 'enum',\n 'extension',\n 'fallthrough',\n 'false',\n 'fileprivate',\n 'for',\n 'func',\n 'guard',\n 'if',\n 'import',\n 'in',\n 'init',\n 'inout',\n 'internal',\n 'is',\n 'let',\n 'nil',\n 'open',\n 'operator',\n 'private',\n 'protocol',\n 'public',\n 'repeat',\n 'rethrows',\n 'return',\n 'self',\n 'static',\n 'struct',\n 'subscript',\n 'super',\n 'switch',\n 'throw',\n 'throws',\n 'true',\n 'try',\n 'typealias',\n 'var',\n 'where',\n 'while',\n]);\n\nconst PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/** Backtick-escapes Kotlin hard keywords (and non-plain identifiers). */\nexport function kotlinIdentifier(name: string): string {\n return KOTLIN_HARD_KEYWORDS.has(name) || !PLAIN_IDENTIFIER.test(name)\n ? `\\`${name}\\``\n : name;\n}\n\n/** Backtick-escapes Swift keywords (and non-plain identifiers). */\nexport function swiftIdentifier(name: string): string {\n return SWIFT_KEYWORDS.has(name) || !PLAIN_IDENTIFIER.test(name)\n ? `\\`${name}\\``\n : name;\n}\n\n/**\n * Emits a Kotlin double-quoted string literal. Escapes `$` (string-template\n * interpolation), quotes, backslashes, and control characters — JSON escaping\n * is NOT Kotlin-safe (`$` stays raw and `\\f` is not a Kotlin escape).\n */\nexport function kotlinStringLiteral(value: string): string {\n let out = '\"';\n for (const char of value) {\n switch (char) {\n case '\\\\':\n out += '\\\\\\\\';\n break;\n case '\"':\n out += '\\\\\"';\n break;\n case '$':\n out += '\\\\$';\n break;\n case '\\n':\n out += '\\\\n';\n break;\n case '\\r':\n out += '\\\\r';\n break;\n case '\\t':\n out += '\\\\t';\n break;\n default: {\n const code = char.codePointAt(0) ?? 0;\n // Control chars and lone surrogates must be \\u-escaped: a raw lone\n // surrogate would be written to disk as U+FFFD, silently corrupting\n // the literal.\n out +=\n code < 0x20 || (code >= 0xd800 && code <= 0xdfff)\n ? `\\\\u${code.toString(16).padStart(4, '0')}`\n : char;\n }\n }\n }\n return `${out}\"`;\n}\n","import { createHash } from 'node:crypto';\nimport {\n SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n SMRT_MOBILE_CONTRACT_VERSION,\n} from './contract-version.js';\nimport { kotlinStringLiteral } from './identifiers.js';\nimport type {\n MobileContract,\n MobileContractField,\n MobileContractObject,\n MobileContractOptions,\n SmrtManifestField,\n SmrtManifestObject,\n} from './types.js';\n\n/**\n * SMRT manifest field type → Kotlin type. Decimals cross the wire as\n * `DecimalString` (string-encoded, no float precision loss); timestamps as\n * nullable `Instant`; JSON payloads as `JsonObject`.\n */\nconst KOTLIN_TYPE_BY_MANIFEST_TYPE: Record<string, string> = {\n id: 'String',\n text: 'String',\n status: 'String',\n foreignKey: 'String',\n crossPackageRef: 'String',\n // 64-bit: SMRT INTEGER columns hold values like byte sizes and epoch\n // millis that overflow a 32-bit Int at decode time.\n integer: 'Long',\n decimal: 'DecimalString?',\n boolean: 'Boolean',\n datetime: 'Instant?',\n json: 'JsonObject',\n};\n\n/**\n * Relationship declarations, not scalar columns — an object row's wire\n * payload does not carry them, so they are excluded from DTO projection.\n */\nconst RELATION_FIELD_TYPES = new Set(['oneToMany', 'manyToMany', 'hasMany']);\n\nconst SWIFT_TYPE_BY_KOTLIN_TYPE: Record<string, string> = {\n String: 'String',\n // Explicit Int64: Swift Int is 32-bit on watchOS arm64_32/armv7k, and the\n // whole point of Long is byte-size/epoch values that overflow 32 bits.\n Long: 'Int64',\n Boolean: 'Bool',\n 'Instant?': 'String?',\n 'DecimalString?': 'String?',\n // Raw JSON string on the Swift side — Swift DTOs are plain structs (no\n // Codable) per the reporter seed; apps parse as needed.\n JsonObject: 'String',\n 'List<String>': '[String]',\n};\n\n/** Name-based fallbacks for manifest fields that carry no `type`. */\nconst BOOLEAN_FIELD_NAMES = new Set([\n 'required',\n 'safetyCritical',\n 'hasSafetyCriticalFallback',\n]);\nconst INTEGER_FIELD_NAMES = new Set([\n 'version',\n 'sortOrder',\n 'precision',\n 'pageStart',\n 'pageEnd',\n 'replayOrder',\n 'clientRevision',\n 'serverRevision',\n]);\nconst STRING_LIST_FIELD_NAMES = new Set([\n 'ruleIds',\n 'systemIds',\n 'supportedLocales',\n 'targetLocales',\n 'requestedLocales',\n 'includedLocales',\n]);\n\nexport function buildMobileContract(\n options: MobileContractOptions,\n): MobileContract {\n const { manifests, allowlist, kotlinPackage, sourceLabel = '' } = options;\n const candidates = manifests.flatMap(listObjects);\n\n const objects: MobileContractObject[] = [];\n const seenQualifiedNames = new Set<string>();\n for (const entry of allowlist) {\n const matches = candidates.filter(\n (object) => object.name === entry || object.qualifiedName === entry,\n );\n if (matches.length === 0) {\n throw new Error(`mobile allowlist references missing object: ${entry}`);\n }\n if (matches.length > 1) {\n const found = matches\n .map((match) => match.qualifiedName ?? match.name)\n .join(', ');\n throw new Error(\n `mobile allowlist entry \"${entry}\" is ambiguous (${found}) — use the qualified name`,\n );\n }\n const qualifiedName = matches[0].qualifiedName ?? matches[0].name;\n if (seenQualifiedNames.has(qualifiedName)) {\n // The same object listed under two spellings (short + qualified) is\n // redundant, not a collision — project it once.\n continue;\n }\n seenQualifiedNames.add(qualifiedName);\n objects.push(toMobileObject(matches[0]));\n }\n\n const dtoNameOwners = new Map<string, string>();\n for (const object of objects) {\n const prior = dtoNameOwners.get(object.dtoName);\n if (prior) {\n throw new Error(\n `mobile allowlist produces duplicate DTO name \"${object.dtoName}\" ` +\n `(${prior} and ${object.qualifiedName}) — generated files would ` +\n 'overwrite each other; allowlisted objects need unique short names',\n );\n }\n dtoNameOwners.set(object.dtoName, object.qualifiedName);\n }\n\n const sourceHash = createHash('sha256')\n .update(JSON.stringify({ allowlist, objects }))\n .digest('hex');\n\n return {\n schemaVersion: SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n contractVersion: SMRT_MOBILE_CONTRACT_VERSION,\n sourceLabel,\n sourceHash,\n objectAllowlist: [...allowlist],\n objectCount: objects.length,\n kotlin: { packageName: kotlinPackage },\n typeMappings: { ...KOTLIN_TYPE_BY_MANIFEST_TYPE },\n objects,\n };\n}\n\nfunction listObjects(manifest: {\n objects: Record<string, SmrtManifestObject> | SmrtManifestObject[];\n}): SmrtManifestObject[] {\n return Array.isArray(manifest.objects)\n ? manifest.objects\n : Object.values(manifest.objects);\n}\n\nfunction toMobileObject(object: SmrtManifestObject): MobileContractObject {\n const fieldEntries = Object.entries(object.fields ?? {});\n const fields: MobileContractField[] = [];\n\n if (!fieldEntries.some(([name]) => name === 'id')) {\n fields.push({\n name: 'id',\n kotlinType: 'String',\n swiftType: 'String',\n nullable: false,\n kotlinDefault: null,\n });\n }\n\n for (const [name, meta] of fieldEntries) {\n if (meta.type && RELATION_FIELD_TYPES.has(meta.type)) {\n continue;\n }\n fields.push(mapField(name, meta));\n }\n\n return {\n name: object.name,\n qualifiedName: object.qualifiedName ?? object.name,\n dtoName: `${object.name}Dto`,\n tableName: object.schema?.tableName ?? null,\n hasTenantId: fieldEntries.some(([name]) => name === 'tenantId'),\n fields,\n };\n}\n\nfunction mapField(name: string, meta: SmrtManifestField): MobileContractField {\n const kotlinType = kotlinTypeFor(name, meta);\n return {\n name,\n kotlinType,\n swiftType: SWIFT_TYPE_BY_KOTLIN_TYPE[kotlinType] ?? 'String',\n nullable: kotlinType.endsWith('?'),\n kotlinDefault: name === 'id' ? null : kotlinDefaultFor(kotlinType, meta),\n };\n}\n\nfunction kotlinTypeFor(name: string, meta: SmrtManifestField): string {\n if (name === 'id') return 'String';\n if (meta.type && KOTLIN_TYPE_BY_MANIFEST_TYPE[meta.type]) {\n return KOTLIN_TYPE_BY_MANIFEST_TYPE[meta.type];\n }\n // Typeless manifest entries: fall back to the name heuristics the amaru\n // seed codegen used.\n if (STRING_LIST_FIELD_NAMES.has(name)) return 'List<String>';\n if (name.endsWith('Json')) return 'JsonObject';\n if (BOOLEAN_FIELD_NAMES.has(name)) return 'Boolean';\n if (INTEGER_FIELD_NAMES.has(name)) return 'Long';\n if (name.endsWith('At') || name.endsWith('On') || name.endsWith('_at'))\n return 'Instant?';\n return 'String';\n}\n\nfunction kotlinDefaultFor(kotlinType: string, meta: SmrtManifestField): string {\n // Current SMRT manifests carry the typed value in `default`; amaru-era\n // manifests carried a (possibly quoted) string in `defaultValue`.\n const declared =\n meta.default !== undefined ? meta.default : meta.defaultValue;\n switch (kotlinType) {\n case 'JsonObject':\n return 'JsonObject(emptyMap())';\n case 'List<String>':\n return 'emptyList()';\n case 'Boolean':\n return declared === true || declared === 'true' ? 'true' : 'false';\n case 'Long':\n return integerDefault(declared);\n case 'Instant?':\n case 'DecimalString?':\n return 'null';\n default:\n return stringDefault(declared);\n }\n}\n\nfunction integerDefault(defaultValue: unknown): string {\n const parsed = Number(String(defaultValue ?? '').replace(/['\"]/g, ''));\n return Number.isFinite(parsed) && String(defaultValue ?? '').trim() !== ''\n ? String(Math.trunc(parsed))\n : '0';\n}\n\nfunction stringDefault(defaultValue: unknown): string {\n if (\n defaultValue === undefined ||\n defaultValue === null ||\n defaultValue === 'null'\n ) {\n return '\"\"';\n }\n\n const text = String(defaultValue).trim();\n if (text === '') {\n return '\"\"';\n }\n if (text.length > 1 && text.startsWith('\"') && text.endsWith('\"')) {\n try {\n const parsed: unknown = JSON.parse(text);\n return typeof parsed === 'string' ? kotlinStringLiteral(parsed) : '\"\"';\n } catch {\n return '\"\"';\n }\n }\n\n const singleQuoted = text.match(/^'([^']*)'$/);\n if (singleQuoted) {\n return kotlinStringLiteral(singleQuoted[1]);\n }\n\n // A raw (unquoted) string value — the shape current SMRT manifests emit.\n return kotlinStringLiteral(text);\n}\n","import {\n SMRT_MOBILE_CONTRACT_SCHEMA_VERSION,\n SMRT_MOBILE_CONTRACT_VERSION,\n} from './contract-version.js';\n\n/**\n * The generated *framework* contract: the stable files checked into\n * `@happyvertical/smrt-mobile` under\n * `src/commonMain/kotlin/com/happyvertical/smrt/mobile/contract/`.\n * This module is their single source of truth — `pnpm generate:framework`\n * rewrites them; the package build verifies freshness.\n */\n\n// One source for the regenerate command in every emitted header — the script\n// is deliberately named generate:framework (a plain `generate` script would\n// be auto-run by turbo's build task and neutralize the freshness gate).\nconst REGENERATE_COMMAND =\n 'pnpm --filter @happyvertical/smrt-mobile-contract generate:framework';\n\nconst HEADER = [\n '// Generated by @happyvertical/smrt-mobile-contract (framework contract).',\n `// Do not edit by hand — regenerate with \\`${REGENERATE_COMMAND}\\`.`,\n '',\n].join('\\n');\n\nconst SUPPORT_TYPES_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\n\n/**\n * Decimal values cross the wire as strings so no precision is lost between\n * the server's DECIMAL columns and platform floating-point types.\n */\ntypealias DecimalString = String\n\n@Serializable\ndata class Measurement(\n val value: DecimalString,\n val unit: String,\n val source: String? = null,\n)\n`;\n\nconst PACK_LOCALIZATION_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonObject\n\n/**\n * Locale policy for an offline pack: which locale was requested, which are\n * included, and where lookups fall back when a translation is missing.\n */\n@Serializable\ndata class PackLanguage(\n val defaultLocale: String = \"en-US\",\n val requestedLocales: List<String> = emptyList(),\n val includedLocales: List<String> = emptyList(),\n val fallbackLocale: String = \"en-US\",\n val policy: String = \"\",\n val reviewState: String = \"\",\n val hasSafetyCriticalFallback: Boolean = false,\n)\n\n/**\n * Provenance of one localized string: where the text came from and whether the\n * translation is reviewed, safety-critical, or a fallback.\n */\n@Serializable\ndata class PackTextSourceRef(\n val sourceLocale: String = \"\",\n val sourceDocumentId: String = \"\",\n val sourceSectionId: String = \"\",\n val sourceFactId: String = \"\",\n val reviewState: String = \"\",\n val safetyCritical: Boolean = false,\n val fallbackReason: String = \"\",\n)\n\n/**\n * One locale's bundle of localized strings for a pack, keyed by text key,\n * with per-key source provenance.\n */\n@Serializable\ndata class PackLanguageBundle(\n val locale: String = \"\",\n val reviewState: String = \"\",\n val textHash: String = \"\",\n val strings: JsonObject = JsonObject(emptyMap()),\n val sourceRefs: Map<String, PackTextSourceRef> = emptyMap(),\n val hasSafetyCriticalFallback: Boolean = false,\n val policy: String = \"\",\n)\n`;\n\nconst MOBILE_AUTH_CONTRACT_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\nimport kotlinx.serialization.json.JsonObject\n\n/**\n * \\`/api/mobile\\` auth + session contract (server-brokered PKCE).\n *\n * The server owns the OIDC/PKCE exchange: \\`auth/start\\` returns the\n * authorization URL plus the \\`state\\`/\\`codeVerifier\\` the client must persist\n * and echo back on \\`auth/complete\\`. SMRT ships the SvelteKit handlers in\n * @happyvertical/smrt-users (issue #1748). App-defined session bootstrap data\n * belongs under MobileSessionBootstrap.extras; unknown top-level fields are\n * outside this contract and are ignored by the Kotlin client.\n */\n\n@Serializable\ndata class MobileUserSummary(\n val id: String,\n val email: String = \"\",\n val label: String = \"\",\n)\n\n@Serializable\ndata class MobileTenantSummary(\n val id: String,\n val name: String = \"\",\n val slug: String = \"\",\n val planName: String = \"\",\n val subscriptionStatus: String = \"\",\n)\n\n@Serializable\ndata class MobileTenantOption(\n val id: String,\n val name: String = \"\",\n val slug: String = \"\",\n val roleSlug: String = \"\",\n val roleLabel: String = \"\",\n)\n\n@Serializable\ndata class MobileAuthProviderSummary(\n val id: String,\n val label: String = \"\",\n val type: String = \"\",\n val supportsPkce: Boolean = false,\n)\n\n@Serializable\ndata class MobileAuthStartRequest(\n val providerId: String? = null,\n val redirectUri: String,\n val scopes: List<String> = emptyList(),\n val state: String? = null,\n val loginHint: String? = null,\n)\n\n@Serializable\ndata class MobileAuthStartResponse(\n val providerId: String,\n val authorizationUrl: String,\n val state: String,\n val codeVerifier: String? = null,\n val nonce: String? = null,\n val redirectUri: String,\n)\n\n@Serializable\ndata class MobileAuthCompleteRequest(\n val providerId: String? = null,\n val code: String,\n val state: String? = null,\n val codeVerifier: String? = null,\n val redirectUri: String,\n)\n\n@Serializable\ndata class MobileAuthSession(\n val accessToken: String,\n val tokenType: String = \"Bearer\",\n val expiresAt: String = \"\",\n val user: MobileUserSummary,\n val activeTenant: MobileTenantOption? = null,\n val tenants: List<MobileTenantOption> = emptyList(),\n)\n\n@Serializable\ndata class MobileSessionBootstrap(\n val user: MobileUserSummary,\n val activeTenant: MobileTenantOption? = null,\n val tenants: List<MobileTenantOption> = emptyList(),\n val extras: JsonObject? = null,\n)\n`;\n\nconst MOBILE_DEVICE_CONTRACT_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nimport kotlinx.serialization.Serializable\n\n@Serializable\ndata class MobileDevicePermissionState(\n val status: String,\n val canRequest: Boolean = false,\n val reason: String? = null,\n)\n\n@Serializable\ndata class MobileDeviceCapability(\n val surface: String,\n val label: String = \"\",\n val supported: Boolean = false,\n val permission: MobileDevicePermissionState,\n val preferredInput: String = \"\",\n)\n\n@Serializable\ndata class MobileDeviceCapabilities(\n val camera: MobileDeviceCapability,\n val microphone: MobileDeviceCapability,\n val checkedAtEpochMillis: Long? = null,\n)\n`;\n\nconst MOBILE_CONTRACT_INFO_KT = `${HEADER}\npackage com.happyvertical.smrt.mobile.contract\n\nconst val SMRT_MOBILE_CONTRACT_SCHEMA_VERSION: Int = ${SMRT_MOBILE_CONTRACT_SCHEMA_VERSION}\nconst val SMRT_MOBILE_CONTRACT_VERSION: String = \"${SMRT_MOBILE_CONTRACT_VERSION}\"\n`;\n\n/** Kotlin framework contract files, keyed by file name. */\nexport function frameworkKotlinFiles(): Map<string, string> {\n return new Map([\n ['SupportTypes.kt', SUPPORT_TYPES_KT],\n ['PackLocalization.kt', PACK_LOCALIZATION_KT],\n ['MobileAuthContract.kt', MOBILE_AUTH_CONTRACT_KT],\n ['MobileDeviceContract.kt', MOBILE_DEVICE_CONTRACT_KT],\n ['MobileContractInfo.kt', MOBILE_CONTRACT_INFO_KT],\n ]);\n}\n\nconst MOBILE_CONTRACT_SWIFT = `// Generated by @happyvertical/smrt-mobile-contract (framework contract).\n// Do not edit by hand — regenerate with \\`${REGENERATE_COMMAND}\\`.\nimport Foundation\n\nlet SmrtMobileContractSchemaVersion = ${SMRT_MOBILE_CONTRACT_SCHEMA_VERSION}\nlet SmrtMobileContractVersion = \"${SMRT_MOBILE_CONTRACT_VERSION}\"\n\n/// Decimal values cross the wire as strings (no precision loss).\ntypealias DecimalString = String\n\nstruct Measurement {\n let value: DecimalString\n let unit: String\n let source: String?\n}\n\nstruct MobileUserSummary {\n let id: String\n let email: String\n let label: String\n}\n\nstruct MobileTenantSummary {\n let id: String\n let name: String\n let slug: String\n let planName: String\n let subscriptionStatus: String\n}\n\nstruct MobileTenantOption {\n let id: String\n let name: String\n let slug: String\n let roleSlug: String\n let roleLabel: String\n}\n\nstruct MobileAuthProviderSummary {\n let id: String\n let label: String\n let type: String\n let supportsPkce: Bool\n}\n\nstruct MobileAuthStartRequest {\n let providerId: String?\n let redirectUri: String\n let scopes: [String]\n let state: String?\n let loginHint: String?\n}\n\nstruct MobileAuthStartResponse {\n let providerId: String\n let authorizationUrl: String\n let state: String\n let codeVerifier: String?\n let nonce: String?\n let redirectUri: String\n}\n\nstruct MobileAuthCompleteRequest {\n let providerId: String?\n let code: String\n let state: String?\n let codeVerifier: String?\n let redirectUri: String\n}\n\nstruct MobileAuthSession {\n let accessToken: String\n let tokenType: String\n let expiresAt: String\n let user: MobileUserSummary\n let activeTenant: MobileTenantOption?\n let tenants: [MobileTenantOption]\n}\n\nstruct MobileSessionBootstrap {\n let user: MobileUserSummary\n let activeTenant: MobileTenantOption?\n let tenants: [MobileTenantOption]\n let extras: String?\n}\n\nstruct MobileDevicePermissionState {\n let status: String\n let canRequest: Bool\n let reason: String?\n}\n\nstruct MobileDeviceCapability {\n let surface: String\n let label: String\n let supported: Bool\n let permission: MobileDevicePermissionState\n let preferredInput: String\n}\n\nstruct MobileDeviceCapabilities {\n let camera: MobileDeviceCapability\n let microphone: MobileDeviceCapability\n let checkedAtEpochMillis: Int64?\n}\n`;\n\n/**\n * Swift mirror of the framework contract for SwiftUI apps (reporter\n * precedent). Not checked into smrt-mobile — iOS wiring lands with\n * smrt-ios (Phase 6); apps can emit it via this API meanwhile.\n */\nexport function frameworkSwiftFiles(): Map<string, string> {\n return new Map([['MobileContract.swift', MOBILE_CONTRACT_SWIFT]]);\n}\n","import { kotlinIdentifier } from './identifiers.js';\nimport type {\n MobileContract,\n MobileContractField,\n MobileContractObject,\n} from './types.js';\n\nexport const GENERATED_HEADER = [\n '// Generated by @happyvertical/smrt-mobile-contract.',\n '// Do not edit by hand.',\n '',\n].join('\\n');\n\nconst FRAMEWORK_CONTRACT_PACKAGE = 'com.happyvertical.smrt.mobile.contract';\n\n/**\n * Emits one Kotlin DTO file per allowlisted object, plus a\n * `MobileContractManifest.kt` recording the contract identity, all in the\n * consumer's Kotlin package. Framework support types (DecimalString, …) are\n * imported from `com.happyvertical.smrt.mobile.contract`.\n */\nexport function generateKotlinDtoFiles(\n contract: MobileContract,\n): Map<string, string> {\n const files = new Map<string, string>();\n\n files.set('MobileContractManifest.kt', manifestFile(contract));\n for (const object of contract.objects) {\n files.set(`${object.dtoName}.kt`, dtoFile(contract, object));\n }\n\n return files;\n}\n\nfunction manifestFile(contract: MobileContract): string {\n return [\n GENERATED_HEADER,\n `package ${contract.kotlin.packageName}`,\n '',\n `const val MOBILE_CONTRACT_SCHEMA_VERSION: Int = ${contract.schemaVersion}`,\n `const val MOBILE_CONTRACT_VERSION: String = \"${contract.contractVersion}\"`,\n `const val MOBILE_CONTRACT_SOURCE_HASH: String = \"${contract.sourceHash}\"`,\n '',\n 'val MOBILE_CONTRACT_OBJECTS: List<String> = listOf(',\n ...contract.objects.map((object) => ` \"${object.name}\",`),\n ')',\n '',\n ].join('\\n');\n}\n\nfunction dtoFile(\n contract: MobileContract,\n object: MobileContractObject,\n): string {\n const imports = new Set(['kotlinx.serialization.Serializable']);\n for (const field of object.fields) {\n if (field.kotlinType.includes('Instant')) {\n imports.add('kotlinx.datetime.Instant');\n }\n if (field.kotlinType.includes('JsonObject')) {\n imports.add('kotlinx.serialization.json.JsonObject');\n }\n if (field.kotlinType.includes('DecimalString')) {\n imports.add(`${FRAMEWORK_CONTRACT_PACKAGE}.DecimalString`);\n }\n }\n\n return [\n GENERATED_HEADER,\n `package ${contract.kotlin.packageName}`,\n '',\n ...[...imports].sort().map((importPath) => `import ${importPath}`),\n '',\n '@Serializable',\n `data class ${object.dtoName}(`,\n ...object.fields.map((field) => kotlinProperty(field)),\n ')',\n '',\n ].join('\\n');\n}\n\nfunction kotlinProperty(field: MobileContractField): string {\n const defaultValue =\n field.kotlinDefault === null ? '' : ` = ${field.kotlinDefault}`;\n return ` val ${kotlinIdentifier(field.name)}: ${field.kotlinType}${defaultValue},`;\n}\n","import { swiftIdentifier } from './identifiers.js';\nimport type { MobileContract, MobileContractField } from './types.js';\n\n/**\n * Emits all allowlisted domain DTOs into a single Swift file. Structs are\n * plain (no Codable) per the reporter seed — apps decode JSON as needed.\n * Kotlin `JsonObject` fields arrive as raw JSON strings on the Swift side.\n */\nexport function generateSwiftDtoFile(contract: MobileContract): string {\n const lines: string[] = [\n '// Generated by @happyvertical/smrt-mobile-contract.',\n '// Do not edit by hand.',\n 'import Foundation',\n '',\n `let MobileContractSchemaVersion = ${contract.schemaVersion}`,\n `let MobileContractVersion = \"${contract.contractVersion}\"`,\n `let MobileContractSourceHash = \"${contract.sourceHash}\"`,\n '',\n ];\n\n for (const object of contract.objects) {\n lines.push(`struct ${object.dtoName} {`);\n for (const field of object.fields) {\n lines.push(` let ${swiftIdentifier(field.name)}: ${swiftType(field)}`);\n }\n lines.push('}', '');\n }\n\n return lines.join('\\n');\n}\n\nfunction swiftType(field: MobileContractField): string {\n return field.swiftType;\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/** Writes every `relPath → content` entry under `rootDir`. */\nexport function writeFileSet(\n rootDir: string,\n files: Map<string, string>,\n): void {\n for (const [relPath, content] of files) {\n const absPath = join(rootDir, relPath);\n mkdirSync(dirname(absPath), { recursive: true });\n writeFileSync(absPath, content);\n }\n}\n\n/**\n * Compares every entry against disk. Returns the list of missing/stale\n * relative paths (empty = fresh).\n */\nexport function verifyFileSet(\n rootDir: string,\n files: Map<string, string>,\n): string[] {\n const stale: string[] = [];\n for (const [relPath, content] of files) {\n const absPath = join(rootDir, relPath);\n if (!existsSync(absPath) || readFileSync(absPath, 'utf8') !== content) {\n stale.push(relPath);\n }\n }\n return stale;\n}\n","/**\n * TypeScript wire types for the `/api/mobile` auth + session contract.\n *\n * These are the SAME shapes the framework Kotlin contract ships to mobile\n * clients (`MOBILE_AUTH_CONTRACT_KT` in `emit-framework.ts`, checked into\n * `@happyvertical/smrt-mobile` as `MobileAuthContract.kt`). Server-side\n * implementations -- the reusable SvelteKit handlers in\n * `@happyvertical/smrt-users` (issue #1748) -- import them from here so the\n * wire contract has one owning package on both sides.\n *\n * Two sync guarantees keep the three representations locked together:\n *\n * 1. Each interface has a `MobileWireShape` descriptor below. The `satisfies`\n * check makes the descriptor fail to COMPILE when it disagrees with the\n * interface (missing/extra field, or wrong required/optional/nullable\n * kind).\n * 2. `__tests__/framework-auth-types.test.ts` parses the Kotlin literal and\n * asserts it matches the descriptors field-for-field, so editing either\n * side without the other fails the contract package's tests.\n *\n * Kotlin → TypeScript field mapping:\n * - `val x: T` (no default) → `x: T` (required)\n * - `val x: T = <non-null value>` → `x?: T` (kotlinx.serialization omits\n * default-equal values by default, and decoders fill absent fields from\n * defaults — so the wire may omit them in either direction)\n * - `val x: T? = null` → `x?: T | null`\n *\n * @packageDocumentation\n */\n\n/**\n * Classifies one wire field. Mirrors the three Kotlin declaration forms the\n * framework contract uses (see the mapping table in the module docs).\n */\nexport type MobileWireFieldKind = 'required' | 'optional' | 'nullable';\n\n/**\n * Compile-time-checked shape descriptor for a wire type: one entry per field,\n * whose kind is DERIVED from the TypeScript declaration. Using\n * `Extract<keyof T, string>` keeps the mapped type non-homomorphic, so every\n * key must be listed (optionality is not copied from `T`) while `T[K]` still\n * carries `undefined` for optional properties.\n */\nexport type MobileWireShape<T> = {\n [K in Extract<keyof T, string>]: undefined extends T[K]\n ? null extends T[K]\n ? 'nullable'\n : 'optional'\n : 'required';\n};\n\n/** Signed-in user summary returned by auth/complete and the session bootstrap. */\nexport interface MobileUserSummary {\n id: string;\n email?: string;\n label?: string;\n}\n\n/** Tenant summary with subscription surface (reserved for app responses). */\nexport interface MobileTenantSummary {\n id: string;\n name?: string;\n slug?: string;\n planName?: string;\n subscriptionStatus?: string;\n}\n\n/** One selectable tenant, labeled with the role the user holds there. */\nexport interface MobileTenantOption {\n id: string;\n name?: string;\n slug?: string;\n roleSlug?: string;\n roleLabel?: string;\n}\n\n/** One configured auth provider (reserved for app-side provider pickers). */\nexport interface MobileAuthProviderSummary {\n id: string;\n label?: string;\n type?: string;\n supportsPkce?: boolean;\n}\n\n/** Body of `POST /api/mobile/auth/start`. */\nexport interface MobileAuthStartRequest {\n providerId?: string | null;\n redirectUri: string;\n scopes?: string[];\n state?: string | null;\n loginHint?: string | null;\n}\n\n/**\n * Response of `POST /api/mobile/auth/start`. The client persists `state` and\n * `codeVerifier` (as an opaque pending handshake) and echoes them back on\n * `auth/complete`; `state` is also validated against the IdP redirect.\n */\nexport interface MobileAuthStartResponse {\n providerId: string;\n authorizationUrl: string;\n state: string;\n codeVerifier?: string | null;\n nonce?: string | null;\n redirectUri: string;\n}\n\n/** Body of `POST /api/mobile/auth/complete`. */\nexport interface MobileAuthCompleteRequest {\n providerId?: string | null;\n code: string;\n state?: string | null;\n codeVerifier?: string | null;\n redirectUri: string;\n}\n\n/**\n * Response of `POST /api/mobile/auth/complete` — the mobile bearer session.\n * `accessToken` goes into `Authorization: Bearer <token>` on every\n * authenticated `/api/mobile` request.\n */\nexport interface MobileAuthSession {\n accessToken: string;\n tokenType?: string;\n expiresAt?: string;\n user: MobileUserSummary;\n activeTenant?: MobileTenantOption | null;\n tenants?: MobileTenantOption[];\n}\n\n/**\n * Response of `GET /api/mobile/session` -- the app-boot payload for a stored\n * bearer. `extras` is the only app-defined extension point and must remain a\n * JSON object (the Kotlin side decodes it as `JsonObject`). App-domain fields\n * placed at the top level are outside the contract and are ignored by the\n * Kotlin decoder.\n */\nexport interface MobileSessionBootstrap {\n user: MobileUserSummary;\n activeTenant?: MobileTenantOption | null;\n tenants?: MobileTenantOption[];\n extras?: Record<string, unknown> | null;\n}\n\nexport const MOBILE_USER_SUMMARY_SHAPE = {\n id: 'required',\n email: 'optional',\n label: 'optional',\n} as const satisfies MobileWireShape<MobileUserSummary>;\n\nexport const MOBILE_TENANT_SUMMARY_SHAPE = {\n id: 'required',\n name: 'optional',\n slug: 'optional',\n planName: 'optional',\n subscriptionStatus: 'optional',\n} as const satisfies MobileWireShape<MobileTenantSummary>;\n\nexport const MOBILE_TENANT_OPTION_SHAPE = {\n id: 'required',\n name: 'optional',\n slug: 'optional',\n roleSlug: 'optional',\n roleLabel: 'optional',\n} as const satisfies MobileWireShape<MobileTenantOption>;\n\nexport const MOBILE_AUTH_PROVIDER_SUMMARY_SHAPE = {\n id: 'required',\n label: 'optional',\n type: 'optional',\n supportsPkce: 'optional',\n} as const satisfies MobileWireShape<MobileAuthProviderSummary>;\n\nexport const MOBILE_AUTH_START_REQUEST_SHAPE = {\n providerId: 'nullable',\n redirectUri: 'required',\n scopes: 'optional',\n state: 'nullable',\n loginHint: 'nullable',\n} as const satisfies MobileWireShape<MobileAuthStartRequest>;\n\nexport const MOBILE_AUTH_START_RESPONSE_SHAPE = {\n providerId: 'required',\n authorizationUrl: 'required',\n state: 'required',\n codeVerifier: 'nullable',\n nonce: 'nullable',\n redirectUri: 'required',\n} as const satisfies MobileWireShape<MobileAuthStartResponse>;\n\nexport const MOBILE_AUTH_COMPLETE_REQUEST_SHAPE = {\n providerId: 'nullable',\n code: 'required',\n state: 'nullable',\n codeVerifier: 'nullable',\n redirectUri: 'required',\n} as const satisfies MobileWireShape<MobileAuthCompleteRequest>;\n\nexport const MOBILE_AUTH_SESSION_SHAPE = {\n accessToken: 'required',\n tokenType: 'optional',\n expiresAt: 'optional',\n user: 'required',\n activeTenant: 'nullable',\n tenants: 'optional',\n} as const satisfies MobileWireShape<MobileAuthSession>;\n\nexport const MOBILE_SESSION_BOOTSTRAP_SHAPE = {\n user: 'required',\n activeTenant: 'nullable',\n tenants: 'optional',\n extras: 'nullable',\n} as const satisfies MobileWireShape<MobileSessionBootstrap>;\n\n/**\n * All auth-contract shape descriptors, keyed by the Kotlin data class name.\n * The parity test asserts this map covers exactly the data classes declared\n * in `MobileAuthContract.kt` — adding a class to either side without the\n * other fails the suite.\n */\nexport const MOBILE_AUTH_WIRE_SHAPES: Record<\n string,\n Record<string, MobileWireFieldKind>\n> = {\n MobileUserSummary: MOBILE_USER_SUMMARY_SHAPE,\n MobileTenantSummary: MOBILE_TENANT_SUMMARY_SHAPE,\n MobileTenantOption: MOBILE_TENANT_OPTION_SHAPE,\n MobileAuthProviderSummary: MOBILE_AUTH_PROVIDER_SUMMARY_SHAPE,\n MobileAuthStartRequest: MOBILE_AUTH_START_REQUEST_SHAPE,\n MobileAuthStartResponse: MOBILE_AUTH_START_RESPONSE_SHAPE,\n MobileAuthCompleteRequest: MOBILE_AUTH_COMPLETE_REQUEST_SHAPE,\n MobileAuthSession: MOBILE_AUTH_SESSION_SHAPE,\n MobileSessionBootstrap: MOBILE_SESSION_BOOTSTRAP_SHAPE,\n};\n"],"mappings":";;;;AACO,IAAM,sCAAsC;AAM5C,IAAM,+BAA+B;;;ACA5C,IAAM,uCAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,iCAAiB,IAAI,IAAI;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AAED,IAAM,mBAAmB;AAGlB,SAAS,iBAAiB,MAAsB;CACrD,OAAO,qBAAqB,IAAI,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,IAChE,KAAK,KAAI,MACT;AACN;AAGO,SAAS,gBAAgB,MAAsB;CACpD,OAAO,eAAe,IAAI,IAAI,KAAK,CAAC,iBAAiB,KAAK,IAAI,IAC1D,KAAK,KAAI,MACT;AACN;AAOO,SAAS,oBAAoB,OAAuB;CACzD,IAAI,MAAM;CACV,KAAA,MAAW,QAAQ,OACjB,QAAQ,MAAR;EACE,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,KAAK;GACH,OAAO;GACP;EACF,SAAS;GACP,MAAM,OAAO,KAAK,YAAY,CAAC,KAAK;GAIpC,OACE,OAAO,MAAS,QAAQ,SAAU,QAAQ,QACtC,MAAM,KAAK,SAAS,EAAE,CAAA,CAAE,SAAS,GAAG,GAAG,MACvC;EACR;CACF;CAEF,OAAO,GAAG,IAAG;AACf;;;AChIA,IAAM,+BAAuD;CAC3D,IAAI;CACJ,MAAM;CACN,QAAQ;CACR,YAAY;CACZ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"}
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "version": "1.0.0",
3
- "timestamp": 1783580561184,
3
+ "timestamp": 1783633795836,
4
4
  "packageName": "@happyvertical/smrt-mobile-contract",
5
- "packageVersion": "0.38.21",
5
+ "packageVersion": "0.38.23",
6
6
  "objects": {},
7
7
  "moduleType": "smrt"
8
8
  }
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-07-09T07:02:42.151Z",
3
+ "generatedAt": "2026-07-09T21:49:56.756Z",
4
4
  "packageName": "@happyvertical/smrt-mobile-contract",
5
- "packageVersion": "0.38.21",
5
+ "packageVersion": "0.38.23",
6
6
  "sourceManifestPath": "dist/manifest.json",
7
7
  "agentDocPath": "AGENTS.md",
8
8
  "sourceHashes": {
9
- "manifest": "8984d44a978964c2cf6d148144a0dc48bd74f5980caa59cfa77f23b0b6ceab12",
10
- "packageJson": "c6e90fbc225959914abcd3409185462376c333d93db640d1d9ae14672e3fdf44",
9
+ "manifest": "c5e4897a2e418f620b6cfc804154aecaf4e5ec51f8bd84cb44594bb08678fcd1",
10
+ "packageJson": "a3a1a6d37444935c84aa79fcf70567f7cc4bdc8fac9912c49c5e2b6d08fc6a53",
11
11
  "agents": "e3769aff93a1a56f2f6862e175a6e35a7996fc5c77c82bfd5cac629711ea8ebe"
12
12
  },
13
13
  "exports": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@happyvertical/smrt-mobile-contract",
3
- "version": "0.38.21",
3
+ "version": "0.38.23",
4
4
  "description": "SMRT mobile contract codegen: manifest + allowlist → mobile-contract.json → Kotlin/Swift DTOs, plus the generated framework contract for @happyvertical/smrt-mobile",
5
5
  "author": "HappyVertical",
6
6
  "license": "MIT",