@sansavision/aurora 0.1.0-alpha.20260212.4

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.
Files changed (150) hide show
  1. package/README.md +4 -0
  2. package/package.json +17 -0
  3. package/src/ai-diagnostics.ts +156 -0
  4. package/src/ai.ts +574 -0
  5. package/src/analyze.ts +669 -0
  6. package/src/bin/aurora.ts +15 -0
  7. package/src/build.ts +431 -0
  8. package/src/bun-test-shims.d.ts +17 -0
  9. package/src/create-feature.ts +419 -0
  10. package/src/create-route.ts +581 -0
  11. package/src/create.ts +425 -0
  12. package/src/dev.ts +126 -0
  13. package/src/devtools.ts +1143 -0
  14. package/src/doctor.ts +611 -0
  15. package/src/explain.ts +855 -0
  16. package/src/help.ts +39 -0
  17. package/src/index.ts +34 -0
  18. package/src/init.ts +1011 -0
  19. package/src/inspect-cache.ts +464 -0
  20. package/src/lsp-inline-hints.ts +254 -0
  21. package/src/node-shims.d.ts +26 -0
  22. package/src/process.d.ts +11 -0
  23. package/src/query-profiler.ts +520 -0
  24. package/src/realtime-monitor.ts +389 -0
  25. package/src/registry.ts +303 -0
  26. package/src/run.ts +37 -0
  27. package/src/start.ts +56 -0
  28. package/src/test.ts +289 -0
  29. package/templates/basic/README.md +16 -0
  30. package/templates/basic/package.json +10 -0
  31. package/templates/basic/src/actions/createMessage.action.server.ts +22 -0
  32. package/templates/basic/src/lib/auth.server.ts +11 -0
  33. package/templates/basic/src/queries/listMessages.server.ts +17 -0
  34. package/templates/basic/src/routes/index.tsx +12 -0
  35. package/templates/blog/README.md +17 -0
  36. package/templates/blog/package.json +12 -0
  37. package/templates/blog/public/assets/og-default.svg +17 -0
  38. package/templates/blog/src/content/loadPosts.server.ts +22 -0
  39. package/templates/blog/src/content/posts/hello-world.md +11 -0
  40. package/templates/blog/src/content/posts/release-notes.md +9 -0
  41. package/templates/blog/src/routes/index.tsx +22 -0
  42. package/templates/blog/src/routes/posts/[slug].tsx +19 -0
  43. package/templates/blog/src/seo/meta.ts +19 -0
  44. package/templates/dashboard/README.md +18 -0
  45. package/templates/dashboard/package.json +10 -0
  46. package/templates/dashboard/src/actions/acknowledgeAlert.action.server.ts +6 -0
  47. package/templates/dashboard/src/queries/getDashboardMetrics.server.ts +30 -0
  48. package/templates/dashboard/src/realtime/useDashboardRealtime.client.ts +13 -0
  49. package/templates/dashboard/src/routes/index.tsx +19 -0
  50. package/templates/dashboard/src/widgets/DataGrid.client.ts +8 -0
  51. package/templates/dashboard/src/widgets/MetricChart.client.ts +8 -0
  52. package/templates/desktop/README.md +18 -0
  53. package/templates/desktop/package.json +11 -0
  54. package/templates/desktop/src/actions/saveDesktopPreference.action.server.ts +28 -0
  55. package/templates/desktop/src/desktop/secureStorage.client.ts +20 -0
  56. package/templates/desktop/src/desktop/tauriBridge.client.ts +14 -0
  57. package/templates/desktop/src/queries/getDesktopSyncStatus.server.ts +9 -0
  58. package/templates/desktop/src/routes/index.tsx +27 -0
  59. package/templates/desktop/src/sync/offlineSyncBoundary.server.ts +27 -0
  60. package/templates/feature-skeleton/README.md +13 -0
  61. package/templates/feature-skeleton/actions/createFeature.action.server.ts +19 -0
  62. package/templates/feature-skeleton/index.ts +8 -0
  63. package/templates/feature-skeleton/queries/listFeature.server.ts +15 -0
  64. package/templates/feature-skeleton/realtime/useFeatureRealtime.client.ts +16 -0
  65. package/templates/feature-skeleton/template.manifest.json +15 -0
  66. package/templates/feature-skeleton/ui/FeatureView.client.tsx +14 -0
  67. package/templates/mobile/README.md +17 -0
  68. package/templates/mobile/package.json +11 -0
  69. package/templates/mobile/src/mobile/auth/session-handoff.client.ts +69 -0
  70. package/templates/mobile/src/mobile/generated/mobile-api-sdk.ts +62 -0
  71. package/templates/mobile/src/mobile/transport/mobile-api-transport.client.ts +122 -0
  72. package/templates/mobile/src/routes/index.tsx +134 -0
  73. package/templates/monorepo/README.md +18 -0
  74. package/templates/monorepo/apps/web/package.json +9 -0
  75. package/templates/monorepo/apps/web/src/routes/index.tsx +1 -0
  76. package/templates/monorepo/package.json +13 -0
  77. package/templates/monorepo/packages/shared/README.md +3 -0
  78. package/templates/monorepo/packages/ui/README.md +3 -0
  79. package/templates/saas/README.md +17 -0
  80. package/templates/saas/package.json +10 -0
  81. package/templates/saas/src/admin/getDashboard.server.ts +18 -0
  82. package/templates/saas/src/auth/session.server.ts +13 -0
  83. package/templates/saas/src/billing/checkout.server.ts +11 -0
  84. package/templates/saas/src/email/sendWelcome.server.ts +8 -0
  85. package/templates/saas/src/realtime/notifications.server.ts +8 -0
  86. package/templates/saas/src/routes/index.tsx +20 -0
  87. package/test/ai.test.ts +94 -0
  88. package/test/analyze.test.ts +301 -0
  89. package/test/build.test.ts +135 -0
  90. package/test/create-feature.test.ts +145 -0
  91. package/test/create-route.test.ts +117 -0
  92. package/test/create.test.ts +222 -0
  93. package/test/dev.test.ts +52 -0
  94. package/test/devtools.test.ts +130 -0
  95. package/test/doctor.test.ts +129 -0
  96. package/test/explain.test.ts +232 -0
  97. package/test/feature-skeleton.test.ts +53 -0
  98. package/test/fixtures/analyze/cache-input.invalid.json +1 -0
  99. package/test/fixtures/analyze/cache-input.missing-keyhash.v1.json +10 -0
  100. package/test/fixtures/analyze/cache-input.unsupported-version.v2.json +10 -0
  101. package/test/fixtures/analyze/cache-input.v1.json +12 -0
  102. package/test/fixtures/analyze/compiler-manifest/manifest.json +11 -0
  103. package/test/fixtures/analyze/guardrails-input.unsupported-version.v2.json +4 -0
  104. package/test/fixtures/analyze/guardrails-input.v1.json +49 -0
  105. package/test/fixtures/analyze/query-input.invalid-cache-status.v1.json +11 -0
  106. package/test/fixtures/analyze/query-input.unsupported-version.v2.json +11 -0
  107. package/test/fixtures/analyze/query-input.v1.json +18 -0
  108. package/test/fixtures/analyze/realtime-input.missing-lag-p95.v1.json +10 -0
  109. package/test/fixtures/analyze/realtime-input.unsupported-version.v2.json +8 -0
  110. package/test/fixtures/analyze/realtime-input.v1.json +12 -0
  111. package/test/fixtures/cache-inspector/cache-input.v1.json +23 -0
  112. package/test/fixtures/cache-inspector/invalid.json +1 -0
  113. package/test/fixtures/cache-inspector/snapshot.v1.json +34 -0
  114. package/test/fixtures/cache-inspector/unsupported-version.v2.json +13 -0
  115. package/test/fixtures/devtools/healthy.v1.json +130 -0
  116. package/test/fixtures/devtools/invalid.json +1 -0
  117. package/test/fixtures/devtools/unsupported-version.v2.json +8 -0
  118. package/test/fixtures/devtools/warn.v1.json +114 -0
  119. package/test/fixtures/doctor/clean/src/page.tsx +3 -0
  120. package/test/fixtures/doctor/findings/src/accessibility.client.tsx +7 -0
  121. package/test/fixtures/doctor/findings/src/migration.config.ts +3 -0
  122. package/test/fixtures/doctor/findings/src/page.client.tsx +5 -0
  123. package/test/fixtures/doctor/findings/src/perf.server.ts +15 -0
  124. package/test/fixtures/doctor/findings/src/routes.js +3 -0
  125. package/test/fixtures/doctor/findings/src/security.server.ts +7 -0
  126. package/test/fixtures/doctor/findings/src/users.server.ts +3 -0
  127. package/test/fixtures/doctor/governance/src/features/analytics/OWNERS.ts +2 -0
  128. package/test/fixtures/doctor/governance/src/features/analytics/page.tsx +3 -0
  129. package/test/fixtures/doctor/governance/src/features/billing/page.tsx +3 -0
  130. package/test/fixtures/explain/invalid.json +1 -0
  131. package/test/fixtures/explain/module-report.unsupported-version.v2.json +6 -0
  132. package/test/fixtures/explain/module-report.v1.json +72 -0
  133. package/test/fixtures/query-profiler/healthy.v1.json +11 -0
  134. package/test/fixtures/query-profiler/invalid.json +1 -0
  135. package/test/fixtures/query-profiler/unsupported-version.v2.json +6 -0
  136. package/test/fixtures/query-profiler/warning.v1.json +10 -0
  137. package/test/fixtures/realtime-monitor/healthy.v1.json +8 -0
  138. package/test/fixtures/realtime-monitor/invalid.json +1 -0
  139. package/test/fixtures/realtime-monitor/unsupported-version.v2.json +8 -0
  140. package/test/fixtures/realtime-monitor/warning.v1.json +8 -0
  141. package/test/help-parity.test.ts +104 -0
  142. package/test/init.test.ts +164 -0
  143. package/test/inspect-cache.test.ts +112 -0
  144. package/test/lsp-inline-hints.test.ts +65 -0
  145. package/test/query-profiler.test.ts +123 -0
  146. package/test/realtime-monitor.test.ts +115 -0
  147. package/test/registry.test.ts +41 -0
  148. package/test/start.test.ts +23 -0
  149. package/test/test-command.test.ts +65 -0
  150. package/tsconfig.json +19 -0
@@ -0,0 +1,30 @@
1
+ export interface MetricPoint {
2
+ label: string;
3
+ value: number;
4
+ }
5
+
6
+ export interface GridRow {
7
+ id: string;
8
+ service: string;
9
+ status: "healthy" | "degraded" | "incident";
10
+ }
11
+
12
+ export interface DashboardMetrics {
13
+ series: MetricPoint[];
14
+ rows: GridRow[];
15
+ pendingAlerts: number;
16
+ }
17
+
18
+ export async function getDashboardMetrics(): Promise<DashboardMetrics> {
19
+ return {
20
+ series: [
21
+ { label: "Requests", value: 1240 },
22
+ { label: "Errors", value: 12 },
23
+ ],
24
+ rows: [
25
+ { id: "svc-api", service: "api", status: "healthy" },
26
+ { id: "svc-search", service: "search", status: "degraded" },
27
+ ],
28
+ pendingAlerts: 1,
29
+ };
30
+ }
@@ -0,0 +1,13 @@
1
+ export interface DashboardRealtimeEvent {
2
+ type: "metrics-updated" | "alert-created" | "alert-acknowledged";
3
+ timestamp: number;
4
+ }
5
+
6
+ export function useDashboardRealtime(
7
+ onEvent: (event: DashboardRealtimeEvent) => void,
8
+ ): () => void {
9
+ const dispose = () => {
10
+ void onEvent;
11
+ };
12
+ return dispose;
13
+ }
@@ -0,0 +1,19 @@
1
+ import { acknowledgeAlert } from "../actions/acknowledgeAlert.action.server";
2
+ import { getDashboardMetrics } from "../queries/getDashboardMetrics.server";
3
+ import { renderDataGrid } from "../widgets/DataGrid.client";
4
+ import { renderMetricChart } from "../widgets/MetricChart.client";
5
+ import { useDashboardRealtime } from "../realtime/useDashboardRealtime.client";
6
+
7
+ export async function HomePage(): Promise<string> {
8
+ const metrics = await getDashboardMetrics();
9
+ const chart = renderMetricChart(metrics.series);
10
+ const grid = renderDataGrid(metrics.rows);
11
+ const unsubscribe = useDashboardRealtime(() => undefined);
12
+
13
+ if (metrics.pendingAlerts > 0) {
14
+ await acknowledgeAlert("alert-1");
15
+ }
16
+
17
+ unsubscribe();
18
+ return `<main><h1>Aurora Dashboard Starter</h1>${chart}${grid}</main>`;
19
+ }
@@ -0,0 +1,8 @@
1
+ import { type GridRow } from "../queries/getDashboardMetrics.server";
2
+
3
+ export function renderDataGrid(rows: ReadonlyArray<GridRow>): string {
4
+ const body = rows
5
+ .map((row) => `<tr><td>${row.service}</td><td>${row.status}</td></tr>`)
6
+ .join("");
7
+ return `<section data-widget="data-grid"><h2>Data Grid</h2><table><thead><tr><th>Service</th><th>Status</th></tr></thead><tbody>${body}</tbody></table></section>`;
8
+ }
@@ -0,0 +1,8 @@
1
+ import { type MetricPoint } from "../queries/getDashboardMetrics.server";
2
+
3
+ export function renderMetricChart(series: ReadonlyArray<MetricPoint>): string {
4
+ const points = series
5
+ .map((point) => `<li>${point.label}: ${point.value}</li>`)
6
+ .join("");
7
+ return `<section data-widget="metric-chart"><h2>Metric Chart</h2><ul>${points}</ul></section>`;
8
+ }
@@ -0,0 +1,18 @@
1
+ # __AURORA_APP_NAME__
2
+
3
+ Template: desktop
4
+
5
+ Aurora desktop starter template.
6
+
7
+ Includes:
8
+
9
+ - Tauri bridge wiring seam for desktop shell calls
10
+ - secure local storage abstraction seam
11
+ - offline-safe sync boundary with queued operation planning
12
+ - query + action example for desktop settings state
13
+
14
+ ## Commands
15
+
16
+ - `aurora dev`
17
+ - `aurora build --target node`
18
+ - `aurora start`
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "__AURORA_APP_NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "aurora dev",
7
+ "build": "aurora build --target node",
8
+ "start": "aurora start",
9
+ "test": "aurora test"
10
+ }
11
+ }
@@ -0,0 +1,28 @@
1
+ import { getDesktopSyncStatus } from "../queries/getDesktopSyncStatus.server";
2
+ import { planOfflineSync } from "../sync/offlineSyncBoundary.server";
3
+
4
+ export interface SaveDesktopPreferenceInput {
5
+ key: string;
6
+ value: string;
7
+ }
8
+
9
+ export interface SaveDesktopPreferenceResult {
10
+ ok: true;
11
+ key: string;
12
+ value: string;
13
+ syncMode: "immediate" | "queued";
14
+ }
15
+
16
+ export async function saveDesktopPreference(
17
+ input: SaveDesktopPreferenceInput,
18
+ ): Promise<SaveDesktopPreferenceResult> {
19
+ const syncStatus = await getDesktopSyncStatus();
20
+ const syncPlan = planOfflineSync(syncStatus);
21
+
22
+ return {
23
+ ok: true,
24
+ key: input.key,
25
+ value: input.value,
26
+ syncMode: syncPlan.mode,
27
+ };
28
+ }
@@ -0,0 +1,20 @@
1
+ import type { TauriBridge } from "./tauriBridge.client";
2
+
3
+ export interface SecureStorage {
4
+ get(key: string): Promise<string | null>;
5
+ set(key: string, value: string): Promise<void>;
6
+ }
7
+
8
+ export function createSecureStorage(bridge: TauriBridge): SecureStorage {
9
+ return {
10
+ async get(key: string): Promise<string | null> {
11
+ const result = await bridge.invoke<{ value?: string | null }>("secure_storage_get", {
12
+ key,
13
+ });
14
+ return result.value ?? null;
15
+ },
16
+ async set(key: string, value: string): Promise<void> {
17
+ await bridge.invoke("secure_storage_set", { key, value });
18
+ },
19
+ };
20
+ }
@@ -0,0 +1,14 @@
1
+ export interface TauriBridge {
2
+ invoke<T>(command: string, payload?: Record<string, unknown>): Promise<T>;
3
+ }
4
+
5
+ export function createTauriBridge(): TauriBridge {
6
+ return {
7
+ async invoke<T>(command: string, payload: Record<string, unknown> = {}): Promise<T> {
8
+ return {
9
+ command,
10
+ payload,
11
+ } as T;
12
+ },
13
+ };
14
+ }
@@ -0,0 +1,9 @@
1
+ import type { OfflineSyncStatus } from "../sync/offlineSyncBoundary.server";
2
+
3
+ export async function getDesktopSyncStatus(): Promise<OfflineSyncStatus> {
4
+ return {
5
+ lastSyncedAt: 1707609600000,
6
+ pendingOperations: 2,
7
+ offlineMode: true,
8
+ };
9
+ }
@@ -0,0 +1,27 @@
1
+ import { saveDesktopPreference } from "../actions/saveDesktopPreference.action.server";
2
+ import { createSecureStorage } from "../desktop/secureStorage.client";
3
+ import { createTauriBridge } from "../desktop/tauriBridge.client";
4
+ import { getDesktopSyncStatus } from "../queries/getDesktopSyncStatus.server";
5
+ import { planOfflineSync } from "../sync/offlineSyncBoundary.server";
6
+
7
+ export async function HomePage(): Promise<string> {
8
+ const syncStatus = await getDesktopSyncStatus();
9
+ const syncPlan = planOfflineSync(syncStatus);
10
+
11
+ const bridge = createTauriBridge();
12
+ const secureStorage = createSecureStorage(bridge);
13
+ await secureStorage.set("desktop-theme", "system");
14
+
15
+ await saveDesktopPreference({
16
+ key: "window.startupView",
17
+ value: "dashboard",
18
+ });
19
+
20
+ return [
21
+ "<main>",
22
+ "<h1>Aurora Desktop Starter</h1>",
23
+ `<p>sync_mode: ${syncPlan.mode}</p>`,
24
+ `<p>pending_operations: ${syncStatus.pendingOperations}</p>`,
25
+ "</main>",
26
+ ].join("");
27
+ }
@@ -0,0 +1,27 @@
1
+ export interface OfflineSyncStatus {
2
+ lastSyncedAt: number;
3
+ pendingOperations: number;
4
+ offlineMode: boolean;
5
+ }
6
+
7
+ export interface OfflineSyncPlan {
8
+ mode: "immediate" | "queued";
9
+ queuedOperationLimit: number;
10
+ reason: string;
11
+ }
12
+
13
+ export function planOfflineSync(status: OfflineSyncStatus): OfflineSyncPlan {
14
+ if (!status.offlineMode && status.pendingOperations === 0) {
15
+ return {
16
+ mode: "immediate",
17
+ queuedOperationLimit: 0,
18
+ reason: "network-online",
19
+ };
20
+ }
21
+
22
+ return {
23
+ mode: "queued",
24
+ queuedOperationLimit: 100,
25
+ reason: status.offlineMode ? "offline-mode" : "pending-operations",
26
+ };
27
+ }
@@ -0,0 +1,13 @@
1
+ # Aurora Feature Skeleton
2
+
3
+ This skeleton is used by `AUR-TPL-004` to define the canonical feature module layout for:
4
+
5
+ - UI (`ui/*.client.tsx`)
6
+ - Query layer (`queries/*.server.ts`)
7
+ - Action layer (`actions/*.action.server.ts`)
8
+ - Realtime hooks (`realtime/*.client.ts`)
9
+
10
+ Placeholders:
11
+
12
+ - `__AURORA_FEATURE_NAME__` for display-oriented naming
13
+ - `__AURORA_FEATURE_SLUG__` for route/cache key style naming
@@ -0,0 +1,19 @@
1
+ export interface Create__AURORA_FEATURE_NAME__Input {
2
+ label: string;
3
+ }
4
+
5
+ export interface __AURORA_FEATURE_NAME__Record {
6
+ id: string;
7
+ label: string;
8
+ createdAt: number;
9
+ }
10
+
11
+ export async function create__AURORA_FEATURE_NAME__(
12
+ input: Create__AURORA_FEATURE_NAME__Input,
13
+ ): Promise<__AURORA_FEATURE_NAME__Record> {
14
+ return {
15
+ id: `__AURORA_FEATURE_SLUG__-${Date.now()}`,
16
+ label: input.label,
17
+ createdAt: Date.now(),
18
+ };
19
+ }
@@ -0,0 +1,8 @@
1
+ const FEATURE_SLUG = "__AURORA_FEATURE_SLUG__";
2
+
3
+ export { FeatureView } from "./ui/FeatureView.client";
4
+ export { list__AURORA_FEATURE_NAME__ } from "./queries/listFeature.server";
5
+ export { create__AURORA_FEATURE_NAME__ } from "./actions/createFeature.action.server";
6
+ export { use__AURORA_FEATURE_NAME__Realtime } from "./realtime/useFeatureRealtime.client";
7
+
8
+ void FEATURE_SLUG;
@@ -0,0 +1,15 @@
1
+ export interface __AURORA_FEATURE_NAME__Record {
2
+ id: string;
3
+ label: string;
4
+ createdAt: number;
5
+ }
6
+
7
+ export async function list__AURORA_FEATURE_NAME__(): Promise<__AURORA_FEATURE_NAME__Record[]> {
8
+ return [
9
+ {
10
+ id: "__AURORA_FEATURE_SLUG__-seed-1",
11
+ label: "seed item",
12
+ createdAt: 1707609600000,
13
+ },
14
+ ];
15
+ }
@@ -0,0 +1,16 @@
1
+ export interface FeatureRealtimeEvent {
2
+ type: "created" | "updated" | "deleted";
3
+ entityId: string;
4
+ }
5
+
6
+ const FEATURE_CHANNEL = "__AURORA_FEATURE_SLUG__";
7
+
8
+ export function use__AURORA_FEATURE_NAME__Realtime(
9
+ onEvent: (event: FeatureRealtimeEvent) => void,
10
+ ): () => void {
11
+ const dispose = () => {
12
+ void onEvent;
13
+ void FEATURE_CHANNEL;
14
+ };
15
+ return dispose;
16
+ }
@@ -0,0 +1,15 @@
1
+ {
2
+ "template": "feature-skeleton",
3
+ "version": 1,
4
+ "placeholders": [
5
+ "__AURORA_FEATURE_NAME__",
6
+ "__AURORA_FEATURE_SLUG__"
7
+ ],
8
+ "modules": [
9
+ "ui/FeatureView.client.tsx",
10
+ "queries/listFeature.server.ts",
11
+ "actions/createFeature.action.server.ts",
12
+ "realtime/useFeatureRealtime.client.ts",
13
+ "index.ts"
14
+ ]
15
+ }
@@ -0,0 +1,14 @@
1
+ export interface __AURORA_FEATURE_NAME__Item {
2
+ id: string;
3
+ label: string;
4
+ createdAt: number;
5
+ }
6
+
7
+ export interface FeatureViewProps {
8
+ items: ReadonlyArray<__AURORA_FEATURE_NAME__Item>;
9
+ onCreate: (label: string) => Promise<void>;
10
+ }
11
+
12
+ export function FeatureView(props: FeatureViewProps): string {
13
+ return `<section data-feature="__AURORA_FEATURE_SLUG__"><h2>__AURORA_FEATURE_NAME__</h2><p>items: ${props.items.length}</p></section>`;
14
+ }
@@ -0,0 +1,17 @@
1
+ # __AURORA_APP_NAME__
2
+
3
+ Template: mobile
4
+
5
+ Aurora mobile companion starter template.
6
+
7
+ Includes:
8
+
9
+ - generated typed API client usage seam (`src/mobile/generated/mobile-api-sdk.ts`)
10
+ - auth token/session handoff strategy for mobile client state (`src/mobile/auth/session-handoff.client.ts`)
11
+ - retry-aware transport for network-limited conditions (`src/mobile/transport/mobile-api-transport.client.ts`)
12
+
13
+ ## Commands
14
+
15
+ - `aurora dev`
16
+ - `aurora build --target node`
17
+ - `aurora start`
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "__AURORA_APP_NAME__",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "aurora dev",
7
+ "build": "aurora build --target node",
8
+ "start": "aurora start",
9
+ "test": "aurora test"
10
+ }
11
+ }
@@ -0,0 +1,69 @@
1
+ const TOKEN_STORAGE_KEY = "aurora.mobile.session.token";
2
+
3
+ export interface MobileSessionToken {
4
+ token: string;
5
+ expiresAt: number | null;
6
+ }
7
+
8
+ export function readSessionToken(): MobileSessionToken | null {
9
+ const storage = resolveStorage();
10
+ if (!storage) {
11
+ return null;
12
+ }
13
+
14
+ const raw = storage.getItem(TOKEN_STORAGE_KEY);
15
+ if (!raw) {
16
+ return null;
17
+ }
18
+
19
+ try {
20
+ const parsed = JSON.parse(raw) as MobileSessionToken;
21
+ if (typeof parsed.token !== "string" || parsed.token.trim().length === 0) {
22
+ return null;
23
+ }
24
+
25
+ if (parsed.expiresAt !== null && typeof parsed.expiresAt !== "number") {
26
+ return null;
27
+ }
28
+
29
+ return {
30
+ token: parsed.token,
31
+ expiresAt: parsed.expiresAt,
32
+ };
33
+ } catch {
34
+ return null;
35
+ }
36
+ }
37
+
38
+ export function writeSessionToken(token: string, expiresAt: number | null): void {
39
+ const storage = resolveStorage();
40
+ if (!storage) {
41
+ return;
42
+ }
43
+
44
+ const normalizedToken = token.trim();
45
+ if (normalizedToken.length === 0) {
46
+ storage.removeItem(TOKEN_STORAGE_KEY);
47
+ return;
48
+ }
49
+
50
+ storage.setItem(
51
+ TOKEN_STORAGE_KEY,
52
+ JSON.stringify({
53
+ token: normalizedToken,
54
+ expiresAt,
55
+ }),
56
+ );
57
+ }
58
+
59
+ function resolveStorage(): Storage | null {
60
+ if (typeof window === "undefined") {
61
+ return null;
62
+ }
63
+
64
+ try {
65
+ return window.localStorage;
66
+ } catch {
67
+ return null;
68
+ }
69
+ }
@@ -0,0 +1,62 @@
1
+ export interface MobileMessageRecord {
2
+ id: string;
3
+ text: string;
4
+ createdAt: number;
5
+ }
6
+
7
+ export interface MobileActionTypes {
8
+ "messages.create": {
9
+ input: {
10
+ text: string;
11
+ };
12
+ result: MobileMessageRecord;
13
+ };
14
+ }
15
+
16
+ export interface MobileQueryTypes {
17
+ "messages.list": {
18
+ params: {
19
+ cursor: string | null;
20
+ limit: number;
21
+ };
22
+ result: {
23
+ items: MobileMessageRecord[];
24
+ nextCursor: string | null;
25
+ };
26
+ };
27
+ }
28
+
29
+ export interface MobileApiTransport {
30
+ callAction<TName extends keyof MobileActionTypes>(
31
+ name: TName,
32
+ input: MobileActionTypes[TName]["input"],
33
+ ): Promise<MobileActionTypes[TName]["result"]>;
34
+ callQuery<TName extends keyof MobileQueryTypes>(
35
+ name: TName,
36
+ params: MobileQueryTypes[TName]["params"],
37
+ ): Promise<MobileQueryTypes[TName]["result"]>;
38
+ }
39
+
40
+ export interface MobileApiClient {
41
+ actions: {
42
+ [TName in keyof MobileActionTypes]: (
43
+ input: MobileActionTypes[TName]["input"],
44
+ ) => Promise<MobileActionTypes[TName]["result"]>;
45
+ };
46
+ queries: {
47
+ [TName in keyof MobileQueryTypes]: (
48
+ params: MobileQueryTypes[TName]["params"],
49
+ ) => Promise<MobileQueryTypes[TName]["result"]>;
50
+ };
51
+ }
52
+
53
+ export function createMobileApiClient(transport: MobileApiTransport): MobileApiClient {
54
+ return {
55
+ actions: {
56
+ "messages.create": (input) => transport.callAction("messages.create", input),
57
+ },
58
+ queries: {
59
+ "messages.list": (params) => transport.callQuery("messages.list", params),
60
+ },
61
+ };
62
+ }
@@ -0,0 +1,122 @@
1
+ import type {
2
+ MobileActionTypes,
3
+ MobileApiTransport,
4
+ MobileQueryTypes,
5
+ } from "../generated/mobile-api-sdk";
6
+ import { readSessionToken } from "../auth/session-handoff.client";
7
+
8
+ export interface MobileTransportOptions {
9
+ baseUrl: string;
10
+ retries?: number;
11
+ retryDelayMs?: number;
12
+ fetchImpl?: typeof fetch;
13
+ }
14
+
15
+ interface MobileResponseEnvelope<TData> {
16
+ data: TData;
17
+ tags?: string[];
18
+ }
19
+
20
+ export function createHttpMobileTransport(
21
+ options: MobileTransportOptions,
22
+ ): MobileApiTransport {
23
+ const fetchImpl = options.fetchImpl ?? fetch;
24
+
25
+ return {
26
+ callAction: async <TName extends keyof MobileActionTypes>(
27
+ name: TName,
28
+ input: MobileActionTypes[TName]["input"],
29
+ ): Promise<MobileActionTypes[TName]["result"]> => {
30
+ const envelope = await postJsonWithRetry<MobileActionTypes[TName]["result"]>(
31
+ fetchImpl,
32
+ `${normalizeBaseUrl(options.baseUrl)}/mobile/action`,
33
+ {
34
+ operation: "action",
35
+ name,
36
+ input,
37
+ session: readSessionToken(),
38
+ },
39
+ {
40
+ retries: options.retries ?? 2,
41
+ retryDelayMs: options.retryDelayMs ?? 75,
42
+ },
43
+ );
44
+
45
+ return envelope.data;
46
+ },
47
+ callQuery: async <TName extends keyof MobileQueryTypes>(
48
+ name: TName,
49
+ params: MobileQueryTypes[TName]["params"],
50
+ ): Promise<MobileQueryTypes[TName]["result"]> => {
51
+ const envelope = await postJsonWithRetry<MobileQueryTypes[TName]["result"]>(
52
+ fetchImpl,
53
+ `${normalizeBaseUrl(options.baseUrl)}/mobile/query`,
54
+ {
55
+ operation: "query",
56
+ name,
57
+ params,
58
+ session: readSessionToken(),
59
+ },
60
+ {
61
+ retries: options.retries ?? 2,
62
+ retryDelayMs: options.retryDelayMs ?? 75,
63
+ },
64
+ );
65
+
66
+ return envelope.data;
67
+ },
68
+ };
69
+ }
70
+
71
+ async function postJsonWithRetry<TData>(
72
+ fetchImpl: typeof fetch,
73
+ url: string,
74
+ payload: unknown,
75
+ retryPolicy: {
76
+ retries: number;
77
+ retryDelayMs: number;
78
+ },
79
+ ): Promise<MobileResponseEnvelope<TData>> {
80
+ const maxAttempts = Math.max(1, retryPolicy.retries + 1);
81
+
82
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
83
+ try {
84
+ const response = await fetchImpl(url, {
85
+ method: "POST",
86
+ headers: {
87
+ "content-type": "application/json",
88
+ },
89
+ body: JSON.stringify(payload),
90
+ });
91
+
92
+ if (!response.ok) {
93
+ if (response.status >= 500 && attempt < maxAttempts) {
94
+ await sleep(retryPolicy.retryDelayMs * attempt);
95
+ continue;
96
+ }
97
+
98
+ throw new Error(`mobile transport request failed: ${response.status}`);
99
+ }
100
+
101
+ const parsed = (await response.json()) as MobileResponseEnvelope<TData>;
102
+ return parsed;
103
+ } catch (error) {
104
+ if (attempt >= maxAttempts) {
105
+ throw error;
106
+ }
107
+
108
+ await sleep(retryPolicy.retryDelayMs * attempt);
109
+ }
110
+ }
111
+
112
+ throw new Error("mobile transport retries exhausted");
113
+ }
114
+
115
+ function normalizeBaseUrl(value: string): string {
116
+ const normalized = value.trim().replace(/\/+$/, "");
117
+ return normalized.length > 0 ? normalized : "https://example.invalid";
118
+ }
119
+
120
+ function sleep(durationMs: number): Promise<void> {
121
+ return new Promise((resolve) => setTimeout(resolve, durationMs));
122
+ }