@lotics/app-sdk 0.5.0 → 0.6.0

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.
@@ -56,4 +56,37 @@ type UseQueryParams<K extends keyof AppQueries & string> = AppQueries[K] extends
56
56
  */
57
57
  export declare function useQuery<K extends keyof AppQueries & string>(alias: K, ...rest: UseQueryParams<K>): QueryState<Record<string, unknown>>;
58
58
  export declare function useQuery(alias: string, params?: Record<string, unknown>): QueryState<Record<string, unknown>>;
59
+ /** A file the host has stored and resolved serving URLs for. */
60
+ export interface UploadedFile {
61
+ id: string;
62
+ filename: string;
63
+ mime_type: string;
64
+ url?: string;
65
+ thumbnail_url?: string;
66
+ }
67
+ interface FileUploadState {
68
+ /**
69
+ * Upload one file. Resolves to the stored file; pass `UploadedFile.id` into
70
+ * a `useWorkflow` call to attach it to a record. Rejects on failure — the
71
+ * file is never partially stored.
72
+ */
73
+ upload: (file: File) => Promise<UploadedFile>;
74
+ /** True while any upload from this hook is in flight. */
75
+ uploading: boolean;
76
+ /** Message of the most recent failed upload, cleared when a new one starts. */
77
+ error: string | null;
78
+ }
79
+ /**
80
+ * Upload files from an app. The bytes are stored via a presigned
81
+ * direct-to-storage upload the host mediates; the API server never proxies
82
+ * them. Works the same in a public (anonymous) app and a member-facing one.
83
+ *
84
+ * ```tsx
85
+ * const { upload, uploading } = useFileUpload();
86
+ * const submit = useWorkflow("submitApplication");
87
+ * const cccd = await upload(file);
88
+ * await submit({ ...fields, cccd_file_id: cccd.id });
89
+ * ```
90
+ */
91
+ export declare function useFileUpload(): FileUploadState;
59
92
  export {};
package/dist/src/hooks.js CHANGED
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Typed React hooks for Lotics app data access.
3
3
  *
4
- * The SDK surface is two hooks: `useQuery` for reads, `useWorkflow` for every
5
- * mutation. App code never writes records directly all writes flow through
6
- * declared workflows, which gives the app owner a typed, audited chokepoint
7
- * and means a publicly-shared app exposes no anonymous direct-write path.
4
+ * The SDK surface is three hooks: `useQuery` for reads, `useWorkflow` for
5
+ * every mutation, and `useFileUpload` for attaching files. App code never
6
+ * writes records directly all writes flow through declared workflows, which
7
+ * gives the app owner a typed, audited chokepoint and means a publicly-shared
8
+ * app exposes no anonymous direct-write path. An uploaded file is inert until
9
+ * a workflow attaches it, so file upload keeps that same property.
8
10
  *
9
11
  * Every hook is a thin wrapper over the postMessage RPC bridge — the parent
10
12
  * does the actual API calls, results stream back through `rpc()`. Hooks manage
@@ -53,3 +55,35 @@ export function useQuery(alias, params) {
53
55
  }, []);
54
56
  return { ...state, refetch };
55
57
  }
58
+ /**
59
+ * Upload files from an app. The bytes are stored via a presigned
60
+ * direct-to-storage upload the host mediates; the API server never proxies
61
+ * them. Works the same in a public (anonymous) app and a member-facing one.
62
+ *
63
+ * ```tsx
64
+ * const { upload, uploading } = useFileUpload();
65
+ * const submit = useWorkflow("submitApplication");
66
+ * const cccd = await upload(file);
67
+ * await submit({ ...fields, cccd_file_id: cccd.id });
68
+ * ```
69
+ */
70
+ export function useFileUpload() {
71
+ const [inFlight, setInFlight] = useState(0);
72
+ const [error, setError] = useState(null);
73
+ const upload = useCallback(async (file) => {
74
+ setInFlight((n) => n + 1);
75
+ setError(null);
76
+ try {
77
+ return await rpc("upload", { file });
78
+ }
79
+ catch (err) {
80
+ const message = err instanceof Error ? err.message : "Upload failed";
81
+ setError(message);
82
+ throw err;
83
+ }
84
+ finally {
85
+ setInFlight((n) => n - 1);
86
+ }
87
+ }, []);
88
+ return { upload, uploading: inFlight > 0, error };
89
+ }
@@ -10,7 +10,8 @@
10
10
  * depending on packages/ui's React Native Web setup.
11
11
  */
12
12
  export { mount } from "./mount.js";
13
- export { useWorkflow, useQuery } from "./hooks.js";
13
+ export { useWorkflow, useQuery, useFileUpload } from "./hooks.js";
14
+ export type { UploadedFile } from "./hooks.js";
14
15
  export { rpc } from "./rpc.js";
15
16
  export type { RpcOp } from "./rpc.js";
16
17
  export type { AppWorkflows, AppQueries } from "./types.js";
package/dist/src/index.js CHANGED
@@ -10,5 +10,5 @@
10
10
  * depending on packages/ui's React Native Web setup.
11
11
  */
12
12
  export { mount } from "./mount.js";
13
- export { useWorkflow, useQuery } from "./hooks.js";
13
+ export { useWorkflow, useQuery, useFileUpload } from "./hooks.js";
14
14
  export { rpc } from "./rpc.js";
package/dist/src/rpc.d.ts CHANGED
@@ -13,5 +13,5 @@
13
13
  * Bumping protocol version requires a coordinated change in the parent.
14
14
  * Add new ops alongside existing ones; never repurpose them.
15
15
  */
16
- export type RpcOp = "query" | "workflow";
16
+ export type RpcOp = "query" | "workflow" | "upload";
17
17
  export declare function rpc<T = unknown>(op: RpcOp, payload: unknown): Promise<T>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lotics/app-sdk",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Runtime SDK for Lotics custom-code apps — typed hooks, postMessage bridge, mount entry point",
5
5
  "type": "module",
6
6
  "exports": {