@gettersethya/mira-client 0.1.0-alpha.2

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 (163) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/client/auth.d.ts +66 -0
  3. package/dist/src/client/auth.d.ts.map +1 -0
  4. package/dist/src/client/auth.js +60 -0
  5. package/dist/src/client/auth.js.map +1 -0
  6. package/dist/src/client/collection.d.ts +239 -0
  7. package/dist/src/client/collection.d.ts.map +1 -0
  8. package/dist/src/client/collection.js +137 -0
  9. package/dist/src/client/collection.js.map +1 -0
  10. package/dist/src/client/errors.d.ts +46 -0
  11. package/dist/src/client/errors.d.ts.map +1 -0
  12. package/dist/src/client/errors.js +40 -0
  13. package/dist/src/client/errors.js.map +1 -0
  14. package/dist/src/client/factory.d.ts +76 -0
  15. package/dist/src/client/factory.d.ts.map +1 -0
  16. package/dist/src/client/factory.js +106 -0
  17. package/dist/src/client/factory.js.map +1 -0
  18. package/dist/src/client/file.d.ts +116 -0
  19. package/dist/src/client/file.d.ts.map +1 -0
  20. package/dist/src/client/file.js +99 -0
  21. package/dist/src/client/file.js.map +1 -0
  22. package/dist/src/client/handler.d.ts +92 -0
  23. package/dist/src/client/handler.d.ts.map +1 -0
  24. package/dist/src/client/handler.js +62 -0
  25. package/dist/src/client/handler.js.map +1 -0
  26. package/dist/src/client/index.d.ts +11 -0
  27. package/dist/src/client/index.d.ts.map +1 -0
  28. package/dist/src/client/index.js +6 -0
  29. package/dist/src/client/index.js.map +1 -0
  30. package/dist/src/client/types.d.ts +138 -0
  31. package/dist/src/client/types.d.ts.map +1 -0
  32. package/dist/src/client/types.js +2 -0
  33. package/dist/src/client/types.js.map +1 -0
  34. package/dist/src/collection/auth.d.ts +79 -0
  35. package/dist/src/collection/auth.d.ts.map +1 -0
  36. package/dist/src/collection/auth.js +50 -0
  37. package/dist/src/collection/auth.js.map +1 -0
  38. package/dist/src/collection/base.d.ts +64 -0
  39. package/dist/src/collection/base.d.ts.map +1 -0
  40. package/dist/src/collection/base.js +40 -0
  41. package/dist/src/collection/base.js.map +1 -0
  42. package/dist/src/collection/bytes.d.ts +51 -0
  43. package/dist/src/collection/bytes.d.ts.map +1 -0
  44. package/dist/src/collection/bytes.js +51 -0
  45. package/dist/src/collection/bytes.js.map +1 -0
  46. package/dist/src/collection/errors.d.ts +29 -0
  47. package/dist/src/collection/errors.d.ts.map +1 -0
  48. package/dist/src/collection/errors.js +23 -0
  49. package/dist/src/collection/errors.js.map +1 -0
  50. package/dist/src/collection/field.d.ts +300 -0
  51. package/dist/src/collection/field.d.ts.map +1 -0
  52. package/dist/src/collection/field.js +175 -0
  53. package/dist/src/collection/field.js.map +1 -0
  54. package/dist/src/collection/index-builder.d.ts +59 -0
  55. package/dist/src/collection/index-builder.d.ts.map +1 -0
  56. package/dist/src/collection/index-builder.js +43 -0
  57. package/dist/src/collection/index-builder.js.map +1 -0
  58. package/dist/src/collection/index.d.ts +22 -0
  59. package/dist/src/collection/index.d.ts.map +1 -0
  60. package/dist/src/collection/index.js +21 -0
  61. package/dist/src/collection/index.js.map +1 -0
  62. package/dist/src/collection/serialize.d.ts +24 -0
  63. package/dist/src/collection/serialize.d.ts.map +1 -0
  64. package/dist/src/collection/serialize.js +127 -0
  65. package/dist/src/collection/serialize.js.map +1 -0
  66. package/dist/src/collection/types.d.ts +119 -0
  67. package/dist/src/collection/types.d.ts.map +1 -0
  68. package/dist/src/collection/types.js +2 -0
  69. package/dist/src/collection/types.js.map +1 -0
  70. package/dist/src/collection/view.d.ts +57 -0
  71. package/dist/src/collection/view.d.ts.map +1 -0
  72. package/dist/src/collection/view.js +49 -0
  73. package/dist/src/collection/view.js.map +1 -0
  74. package/dist/src/filter/builder.d.ts +127 -0
  75. package/dist/src/filter/builder.d.ts.map +1 -0
  76. package/dist/src/filter/builder.js +72 -0
  77. package/dist/src/filter/builder.js.map +1 -0
  78. package/dist/src/filter/compiler.d.ts +48 -0
  79. package/dist/src/filter/compiler.d.ts.map +1 -0
  80. package/dist/src/filter/compiler.js +132 -0
  81. package/dist/src/filter/compiler.js.map +1 -0
  82. package/dist/src/filter/index.d.ts +6 -0
  83. package/dist/src/filter/index.d.ts.map +1 -0
  84. package/dist/src/filter/index.js +4 -0
  85. package/dist/src/filter/index.js.map +1 -0
  86. package/dist/src/filter/types.d.ts +96 -0
  87. package/dist/src/filter/types.d.ts.map +1 -0
  88. package/dist/src/filter/types.js +44 -0
  89. package/dist/src/filter/types.js.map +1 -0
  90. package/dist/src/index.d.ts +3 -0
  91. package/dist/src/index.d.ts.map +1 -0
  92. package/dist/src/index.js +3 -0
  93. package/dist/src/index.js.map +1 -0
  94. package/dist/src/rule/builder.d.ts +97 -0
  95. package/dist/src/rule/builder.d.ts.map +1 -0
  96. package/dist/src/rule/builder.js +126 -0
  97. package/dist/src/rule/builder.js.map +1 -0
  98. package/dist/src/rule/index.d.ts +3 -0
  99. package/dist/src/rule/index.d.ts.map +1 -0
  100. package/dist/src/rule/index.js +3 -0
  101. package/dist/src/rule/index.js.map +1 -0
  102. package/dist/src/rule/types.d.ts +129 -0
  103. package/dist/src/rule/types.d.ts.map +1 -0
  104. package/dist/src/rule/types.js +2 -0
  105. package/dist/src/rule/types.js.map +1 -0
  106. package/dist/tests/client/auth.test.d.ts +2 -0
  107. package/dist/tests/client/auth.test.d.ts.map +1 -0
  108. package/dist/tests/client/auth.test.js +67 -0
  109. package/dist/tests/client/auth.test.js.map +1 -0
  110. package/dist/tests/client/collection.test.d.ts +2 -0
  111. package/dist/tests/client/collection.test.d.ts.map +1 -0
  112. package/dist/tests/client/collection.test.js +543 -0
  113. package/dist/tests/client/collection.test.js.map +1 -0
  114. package/dist/tests/client/file.test.d.ts +2 -0
  115. package/dist/tests/client/file.test.d.ts.map +1 -0
  116. package/dist/tests/client/file.test.js +21 -0
  117. package/dist/tests/client/file.test.js.map +1 -0
  118. package/dist/tests/client/handler.test.d.ts +2 -0
  119. package/dist/tests/client/handler.test.d.ts.map +1 -0
  120. package/dist/tests/client/handler.test.js +24 -0
  121. package/dist/tests/client/handler.test.js.map +1 -0
  122. package/dist/tests/client/types.test.d.ts +2 -0
  123. package/dist/tests/client/types.test.d.ts.map +1 -0
  124. package/dist/tests/client/types.test.js +168 -0
  125. package/dist/tests/client/types.test.js.map +1 -0
  126. package/dist/tests/collection/auth.test.d.ts +2 -0
  127. package/dist/tests/collection/auth.test.d.ts.map +1 -0
  128. package/dist/tests/collection/auth.test.js +44 -0
  129. package/dist/tests/collection/auth.test.js.map +1 -0
  130. package/dist/tests/collection/base.test.d.ts +2 -0
  131. package/dist/tests/collection/base.test.d.ts.map +1 -0
  132. package/dist/tests/collection/base.test.js +70 -0
  133. package/dist/tests/collection/base.test.js.map +1 -0
  134. package/dist/tests/collection/bytes.test.d.ts +2 -0
  135. package/dist/tests/collection/bytes.test.d.ts.map +1 -0
  136. package/dist/tests/collection/bytes.test.js +9 -0
  137. package/dist/tests/collection/bytes.test.js.map +1 -0
  138. package/dist/tests/collection/field.test.d.ts +2 -0
  139. package/dist/tests/collection/field.test.d.ts.map +1 -0
  140. package/dist/tests/collection/field.test.js +140 -0
  141. package/dist/tests/collection/field.test.js.map +1 -0
  142. package/dist/tests/collection/index-type.test.d.ts +2 -0
  143. package/dist/tests/collection/index-type.test.d.ts.map +1 -0
  144. package/dist/tests/collection/index-type.test.js +28 -0
  145. package/dist/tests/collection/index-type.test.js.map +1 -0
  146. package/dist/tests/collection/serialize.test.d.ts +2 -0
  147. package/dist/tests/collection/serialize.test.d.ts.map +1 -0
  148. package/dist/tests/collection/serialize.test.js +165 -0
  149. package/dist/tests/collection/serialize.test.js.map +1 -0
  150. package/dist/tests/collection/view.test.d.ts +2 -0
  151. package/dist/tests/collection/view.test.d.ts.map +1 -0
  152. package/dist/tests/collection/view.test.js +66 -0
  153. package/dist/tests/collection/view.test.js.map +1 -0
  154. package/dist/tests/filter/compiler.test.d.ts +2 -0
  155. package/dist/tests/filter/compiler.test.d.ts.map +1 -0
  156. package/dist/tests/filter/compiler.test.js +156 -0
  157. package/dist/tests/filter/compiler.test.js.map +1 -0
  158. package/dist/tests/rule/builder.test.d.ts +2 -0
  159. package/dist/tests/rule/builder.test.d.ts.map +1 -0
  160. package/dist/tests/rule/builder.test.js +290 -0
  161. package/dist/tests/rule/builder.test.js.map +1 -0
  162. package/dist/tsconfig.tsbuildinfo +1 -0
  163. package/package.json +39 -0
@@ -0,0 +1,76 @@
1
+ import type { AnyCollectionDef, FieldsMap } from "@gettersethya/mira-collection";
2
+ import type { BrowserAuth, ServerAuth } from "./auth.js";
3
+ import type { CollectionClient, RetryOptions } from "./collection.js";
4
+ import type { ClientHandler } from "./handler.js";
5
+ import type { InferRecord } from "./types.js";
6
+ type CollectionAdapter = <F extends FieldsMap>(client: CollectionClient<F>, name: string) => CollectionClient<F>;
7
+ type BaseClient<A extends BrowserAuth | ServerAuth = BrowserAuth | ServerAuth> = {
8
+ collection<C extends AnyCollectionDef>(def: C): CollectionClient<C["fields"]>;
9
+ auth: A;
10
+ withCollections<M extends Record<string, AnyCollectionDef>>(map: M, opts?: {
11
+ adapter?: CollectionAdapter;
12
+ }): BaseClient<A> & CollectionAccessors<M>;
13
+ };
14
+ type CollectionAccessors<M extends Record<string, AnyCollectionDef>> = {
15
+ [K in keyof M]: CollectionClient<M[K]["fields"]> & (M[K]["schema"]["x-collection-kind"] extends "auth" ? {
16
+ authWithPassword(): ClientHandler<{
17
+ token: string;
18
+ record: InferRecord<M[K]["fields"]>;
19
+ }, {
20
+ email: string;
21
+ password: string;
22
+ }>;
23
+ } : {});
24
+ };
25
+ type BrowserMiraClient = BaseClient<BrowserAuth>;
26
+ type ServerMiraClient = BaseClient<ServerAuth>;
27
+ /**
28
+ * Create a Mira client instance for the browser (default mode).
29
+ * Uses cookies for auth — the client tracks login state via `auth.isLoggedIn()`.
30
+ *
31
+ * @param baseUrl - Base URL of the Mira server (default: "/" for same-origin)
32
+ * @param opts.defaultRetryOptions - Optional default retry schedule for all operations
33
+ * @returns A BrowserMiraClient with `auth: BrowserAuth`
34
+ *
35
+ * @example
36
+ * import { createMiraClient } from "@gettersethya/mira-client"
37
+ *
38
+ * const mira = createMiraClient("http://localhost:3000")
39
+ * const posts = await mira.posts.getList().raw()
40
+ *
41
+ * @example
42
+ * // With retry options
43
+ * import { Schedule } from "effect"
44
+ * const mira = createMiraClient("/", {
45
+ * defaultRetryOptions: { schedule: Schedule.exponential(100) }
46
+ * })
47
+ */
48
+ export declare function createMiraClient(baseUrl?: string, opts?: {
49
+ type?: "browser";
50
+ defaultRetryOptions?: RetryOptions;
51
+ }): BrowserMiraClient;
52
+ /**
53
+ * Create a Mira client instance for server-side rendering (SSR).
54
+ * Uses `type: "server"` to enable manual JWT token management via `auth: ServerAuth`.
55
+ *
56
+ * In server mode, the client does NOT track login state — you must call
57
+ * `mira.auth.setToken(token)` explicitly with the JWT from your SSR context.
58
+ *
59
+ * @param baseUrl - Base URL of the Mira server
60
+ * @param opts.type - Must be "server"
61
+ * @param opts.defaultRetryOptions - Optional default retry schedule
62
+ * @returns A ServerMiraClient with `auth: ServerAuth`
63
+ *
64
+ * @example
65
+ * import { createMiraClient } from "@gettersethya/mira-client"
66
+ *
67
+ * const mira = createMiraClient("http://localhost:3000", { type: "server" })
68
+ * mira.auth.setToken("eyJ...") // set token from SSR context
69
+ * const posts = await mira.posts.getList().raw()
70
+ */
71
+ export declare function createMiraClient(baseUrl: string | undefined, opts: {
72
+ type: "server";
73
+ defaultRetryOptions?: RetryOptions;
74
+ }): ServerMiraClient;
75
+ export {};
76
+ //# sourceMappingURL=factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.d.ts","sourceRoot":"","sources":["../../../src/client/factory.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAA;AAChF,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AAExD,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAGrE,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,cAAc,CAAA;AAE5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C,KAAK,iBAAiB,GAAG,CAAC,CAAC,SAAS,SAAS,EAC3C,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC,EAC3B,IAAI,EAAE,MAAM,KACT,gBAAgB,CAAC,CAAC,CAAC,CAAA;AAExB,KAAK,UAAU,CAAC,CAAC,SAAS,WAAW,GAAG,UAAU,GAAG,WAAW,GAAG,UAAU,IAAI;IAC/E,UAAU,CAAC,CAAC,SAAS,gBAAgB,EAAE,GAAG,EAAE,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC7E,IAAI,EAAE,CAAC,CAAA;IACP,eAAe,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,EACxD,GAAG,EAAE,CAAC,EACN,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,iBAAiB,CAAA;KAAE,GACrC,UAAU,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,CAAA;CAC1C,CAAA;AAED,KAAK,mBAAmB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,IAAI;KACpE,CAAC,IAAI,MAAM,CAAC,GAAG,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAC9C,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,mBAAmB,CAAC,SAAS,MAAM,GAC/C;QACE,gBAAgB,IAAI,aAAa,CAC/B;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;SAAE,EACtD;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CACpC,CAAA;KACF,GACD,EAAE,CAAC;CACV,CAAA;AAED,KAAK,iBAAiB,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;AAEhD,KAAK,gBAAgB,GAAG,UAAU,CAAC,UAAU,CAAC,CAAA;AAwG9C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,IAAI,CAAC,EAAE,SAAS,CAAC;IAAC,mBAAmB,CAAC,EAAE,YAAY,CAAA;CAAE,GAC9D,iBAAiB,CAAA;AAEpB;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,IAAI,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,mBAAmB,CAAC,EAAE,YAAY,CAAA;CAAE,GAC3D,gBAAgB,CAAA"}
@@ -0,0 +1,106 @@
1
+ import { HttpClient, HttpClientRequest as HCR } from "@effect/platform";
2
+ import { Effect, MutableRef } from "effect";
3
+ import { makeBrowserAuth, makeServerAuth } from "./auth.js";
4
+ import { makeCollectionClient } from "./collection.js";
5
+ import { MiraError } from "./errors.js";
6
+ import { makeClientHandler as makeHandler } from "./handler.js";
7
+ function catchAllErrors(effect) {
8
+ return effect.pipe(Effect.catchTags({
9
+ RequestError: (e) => Effect.fail(new MiraError({ status: 0, body: e.message })),
10
+ ResponseError: (e) => Effect.fail(new MiraError({ status: e.response.status, body: e.message })),
11
+ HttpBodyError: (e) => Effect.fail(new MiraError({ status: 500, body: String(e) }))
12
+ }));
13
+ }
14
+ function createExecute(baseUrl, authTokenRef) {
15
+ return (req) => {
16
+ const raw = Effect.gen(function* () {
17
+ const http = yield* HttpClient.HttpClient;
18
+ const token = authTokenRef ? MutableRef.get(authTokenRef) : null;
19
+ const finalReq = HCR.prependUrl(baseUrl)(token ? HCR.setHeader(req, "Authorization", `Bearer ${token}`) : req);
20
+ const res = yield* http.execute(finalReq);
21
+ if (res.status >= 400) {
22
+ const body = yield* res.json;
23
+ return yield* new MiraError({ status: res.status, body });
24
+ }
25
+ if (res.status === 204) {
26
+ return undefined;
27
+ }
28
+ return (yield* res.json);
29
+ });
30
+ return catchAllErrors(raw);
31
+ };
32
+ }
33
+ function createMiraClientInternal(baseUrl = "/", type = "browser", defaultRetryOptions) {
34
+ const fileTokenCacheRef = MutableRef.make(new Map());
35
+ const authTokenRef = type === "server" ? MutableRef.make(null) : null;
36
+ const loggedInRef = type === "browser" ? MutableRef.make(false) : null;
37
+ const execute = createExecute(baseUrl, authTokenRef);
38
+ const makeClientHandler = (effect) => makeHandler(effect);
39
+ function buildCollectionClient(def) {
40
+ const isAuth = def.schema["x-collection-kind"] === "auth";
41
+ return makeCollectionClient({
42
+ collectionName: def.name,
43
+ schema: def.schema,
44
+ fields: def.fields,
45
+ execute,
46
+ baseUrl,
47
+ authTokenRef,
48
+ loggedInRef,
49
+ fileTokenCacheRef,
50
+ isAuth,
51
+ ...(defaultRetryOptions !== undefined ? { defaultRetryOptions } : {})
52
+ });
53
+ }
54
+ function makeAuth() {
55
+ if (type === "server" && authTokenRef !== null) {
56
+ return makeServerAuth(authTokenRef, fileTokenCacheRef);
57
+ }
58
+ if (loggedInRef !== null) {
59
+ return makeBrowserAuth(execute, makeClientHandler, loggedInRef);
60
+ }
61
+ return makeBrowserAuth(execute, makeClientHandler, MutableRef.make(false));
62
+ }
63
+ function withCollections(map, opts) {
64
+ const baseClient = buildBaseClient();
65
+ const collections = {};
66
+ for (const key of Object.keys(map)) {
67
+ const raw = buildCollectionClient(map[key]);
68
+ collections[key] = (opts?.adapter ? opts.adapter(raw, String(key)) : raw);
69
+ }
70
+ return Object.assign(baseClient, collections);
71
+ }
72
+ function buildBaseClient() {
73
+ return {
74
+ collection: (def) => buildCollectionClient(def),
75
+ auth: makeAuth(),
76
+ withCollections
77
+ };
78
+ }
79
+ return buildBaseClient();
80
+ }
81
+ /**
82
+ * Create a Mira client instance.
83
+ *
84
+ * In browser mode (default), auth is cookie-based and `isLoggedIn()` reflects
85
+ * client-side login state. In server mode, you manage the JWT token explicitly
86
+ * via `auth.setToken()`.
87
+ *
88
+ * Use `withCollections()` to create typed accessors for each collection:
89
+ *
90
+ * @example
91
+ * const mira = createMiraClient("/").withCollections({ posts, users })
92
+ * const posts = await mira.posts.getList().raw()
93
+ * const loginResult = await mira.users.authWithPassword().raw({
94
+ * email: "admin@test.com",
95
+ * password: "admin1234"
96
+ * })
97
+ *
98
+ * @see BrowserAuth — client-side auth
99
+ * @see ServerAuth — server-side auth
100
+ * @see CollectionClient — the per-collection client interface
101
+ */
102
+ export function createMiraClient(baseUrl = "/", opts = {}) {
103
+ const { type = "browser", defaultRetryOptions } = opts;
104
+ return createMiraClientInternal(baseUrl, type, defaultRetryOptions);
105
+ }
106
+ //# sourceMappingURL=factory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"factory.js","sourceRoot":"","sources":["../../../src/client/factory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,116 @@
1
+ import type { HttpClient } from "@effect/platform";
2
+ import { Effect, MutableRef } from "effect";
3
+ import type { AnyCollectionDef, FieldsMap } from "@gettersethya/mira-collection";
4
+ import { MiraError } from "./errors.js";
5
+ import type { ClientHandler, ExecuteFn } from "./handler.js";
6
+ import type { CollectionFileFields } from "./types.js";
7
+ /**
8
+ * Build a file request URL for the Mira file-serving endpoint.
9
+ *
10
+ * Constructs a URL of the form:
11
+ * `{baseUrl}/api/files/{collection}/{id}/{filename}?token={token}&thumb={spec}`
12
+ *
13
+ * Query parameters are only appended when defined.
14
+ *
15
+ * @param baseUrl - The base URL of the Mira server (e.g., "http://localhost:3000" or "/" for same-origin)
16
+ * @param collection - The collection name
17
+ * @param id - The record ID
18
+ * @param filename - The filename as stored in the record's file field
19
+ * @param opts.token - Optional short-lived JWT token for protected files
20
+ * @param opts.thumb - Optional thumbnail spec (e.g., "200x200", "100x100_fit")
21
+ * @returns The fully constructed file URL string
22
+ *
23
+ * @example
24
+ * buildFileUrl("/", "posts", "abc123", "cover.jpg")
25
+ * // "/api/files/posts/abc123/cover.jpg"
26
+ *
27
+ * @example
28
+ * buildFileUrl("http://localhost:8090", "users", "u1", "avatar.png", {
29
+ * token: "eyJ...",
30
+ * thumb: "200x200"
31
+ * })
32
+ * // "http://localhost:8090/api/files/users/u1/avatar.png?token=eyJ...&thumb=200x200"
33
+ */
34
+ export declare function buildFileUrl(baseUrl: string, collection: string, id: string, filename: string, opts?: {
35
+ token?: string;
36
+ thumb?: string;
37
+ }): string;
38
+ /**
39
+ * Create a function that retrieves a short-lived file-access JWT token for a collection.
40
+ * Tokens are cached per collection with a 30-second freshness buffer before expiry.
41
+ *
42
+ * @internal Called internally by `makeFileFields`. Use `ProtectedFileFieldClient.getToken()` instead.
43
+ */
44
+ export declare function makeGetFileToken(execute: ExecuteFn, cacheRef: MutableRef.MutableRef<Map<string, {
45
+ token: string;
46
+ expiresAt: number;
47
+ }>>): (collection: string) => Effect.Effect<string, MiraError, HttpClient.HttpClient>;
48
+ /**
49
+ * Client interface for a public (non-protected) file field.
50
+ * File URLs are constructed synchronously because no auth token is needed.
51
+ *
52
+ * @example
53
+ * // Given a post with a public "cover" file field
54
+ * const url = post.fields.cover.url(post.id, "cover.jpg")
55
+ * // "/api/files/posts/abc/cover.jpg"
56
+ *
57
+ * @example
58
+ * // With thumbnail variant
59
+ * const thumbUrl = post.fields.cover.url(post.id, "cover.jpg", { thumb: "200x200" })
60
+ *
61
+ * @see ProtectedFileFieldClient — file client that requires auth tokens
62
+ * @see buildFileUrl — underlying URL builder
63
+ */
64
+ export type PublicFileFieldClient = {
65
+ isProtected: false;
66
+ url(id: string, filename: string, opts?: {
67
+ thumb?: string;
68
+ }): string;
69
+ };
70
+ /**
71
+ * Client interface for a protected file field.
72
+ * Protected files require a short-lived JWT token to access.
73
+ *
74
+ * Three modes:
75
+ * - `url(id, filename, { token })` — synchronous URL construction when you already
76
+ * have a token (e.g., from `getToken()`)
77
+ * - `asyncUrl(id, filename)` — returns a `ClientHandler<string>` that fetches a token
78
+ * and constructs the URL in one async call
79
+ * - `getToken()` — fetches a fresh file-access token (cached for 30s by collection)
80
+ *
81
+ * @example
82
+ * // Async URL construction (fetches token automatically)
83
+ * const url = await post.fields.avatar.asyncUrl(post.id, "avatar.png").raw()
84
+ *
85
+ * @example
86
+ * // Manual token management for batch operations
87
+ * const token = await post.fields.avatar.getToken().raw()
88
+ * const u1 = post.fields.avatar.url("id1", "img1.png", { token })
89
+ * const u2 = post.fields.avatar.url("id2", "img2.png", { token })
90
+ *
91
+ * @see PublicFileFieldClient — file client for unprotected files
92
+ * @see buildFileUrl — underlying URL builder
93
+ */
94
+ export type ProtectedFileFieldClient = {
95
+ isProtected: true;
96
+ asyncUrl(id: string, filename: string, opts?: {
97
+ thumb?: string;
98
+ }): ClientHandler<string>;
99
+ url(id: string, filename: string, opts: {
100
+ token: string;
101
+ thumb?: string;
102
+ }): string;
103
+ getToken(): ClientHandler<string>;
104
+ };
105
+ /**
106
+ * Build the file field client interfaces for a collection.
107
+ * Iterates over the schema's properties, creating `PublicFileFieldClient` or
108
+ * `ProtectedFileFieldClient` instances for each `file`-kind field.
109
+ *
110
+ * @internal Called by `makeCollectionClient` — use `collection.fields` instead.
111
+ */
112
+ export declare function makeFileFields<F extends FieldsMap>(collection: AnyCollectionDef, schema: AnyCollectionDef["schema"], baseUrl: string, execute: ExecuteFn, cacheRef: MutableRef.MutableRef<Map<string, {
113
+ token: string;
114
+ expiresAt: number;
115
+ }>>, makeClientHandler: <T>(effect: Effect.Effect<T, MiraError, HttpClient.HttpClient>) => ClientHandler<T>): CollectionFileFields<F>;
116
+ //# sourceMappingURL=file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.d.ts","sourceRoot":"","sources":["../../../src/client/file.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAqB,MAAM,kBAAkB,CAAA;AAGrE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAC3C,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAA;AAChF,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAEtD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,UAAU,EAAE,MAAM,EAClB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GACxC,MAAM,CAOR;AAcD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,GACjF,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,CAkBjF;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,WAAW,EAAE,KAAK,CAAA;IAClB,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;CACrE,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,MAAM,wBAAwB,GAAG;IACrC,WAAW,EAAE,IAAI,CAAA;IACjB,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,aAAa,CAAC,MAAM,CAAC,CAAA;IACxF,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,MAAM,CAAA;IAClF,QAAQ,IAAI,aAAa,CAAC,MAAM,CAAC,CAAA;CAClC,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAChD,UAAU,EAAE,gBAAgB,EAC5B,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,EAClC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,EAClB,QAAQ,EAAE,UAAU,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,EAClF,iBAAiB,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,KAAK,aAAa,CAAC,CAAC,CAAC,GACrG,oBAAoB,CAAC,CAAC,CAAC,CAoCzB"}
@@ -0,0 +1,99 @@
1
+ import { HttpClientRequest as HCR } from "@effect/platform";
2
+ import { Effect, MutableRef } from "effect";
3
+ import { MiraError } from "./errors.js";
4
+ /**
5
+ * Build a file request URL for the Mira file-serving endpoint.
6
+ *
7
+ * Constructs a URL of the form:
8
+ * `{baseUrl}/api/files/{collection}/{id}/{filename}?token={token}&thumb={spec}`
9
+ *
10
+ * Query parameters are only appended when defined.
11
+ *
12
+ * @param baseUrl - The base URL of the Mira server (e.g., "http://localhost:3000" or "/" for same-origin)
13
+ * @param collection - The collection name
14
+ * @param id - The record ID
15
+ * @param filename - The filename as stored in the record's file field
16
+ * @param opts.token - Optional short-lived JWT token for protected files
17
+ * @param opts.thumb - Optional thumbnail spec (e.g., "200x200", "100x100_fit")
18
+ * @returns The fully constructed file URL string
19
+ *
20
+ * @example
21
+ * buildFileUrl("/", "posts", "abc123", "cover.jpg")
22
+ * // "/api/files/posts/abc123/cover.jpg"
23
+ *
24
+ * @example
25
+ * buildFileUrl("http://localhost:8090", "users", "u1", "avatar.png", {
26
+ * token: "eyJ...",
27
+ * thumb: "200x200"
28
+ * })
29
+ * // "http://localhost:8090/api/files/users/u1/avatar.png?token=eyJ...&thumb=200x200"
30
+ */
31
+ export function buildFileUrl(baseUrl, collection, id, filename, opts) {
32
+ const base = `${baseUrl}/api/files/${collection}/${id}/${filename}`;
33
+ const params = new URLSearchParams();
34
+ if (opts?.token !== undefined)
35
+ params.set("token", opts.token);
36
+ if (opts?.thumb !== undefined)
37
+ params.set("thumb", opts.thumb);
38
+ const qs = params.toString();
39
+ return qs ? `${base}?${qs}` : base;
40
+ }
41
+ function toExecuteEffect(reqEffect, execute) {
42
+ return reqEffect.pipe(Effect.flatMap((req) => execute(req)), Effect.catchTag("HttpBodyError", (e) => Effect.fail(new MiraError({ status: 500, body: String(e) }))));
43
+ }
44
+ /**
45
+ * Create a function that retrieves a short-lived file-access JWT token for a collection.
46
+ * Tokens are cached per collection with a 30-second freshness buffer before expiry.
47
+ *
48
+ * @internal Called internally by `makeFileFields`. Use `ProtectedFileFieldClient.getToken()` instead.
49
+ */
50
+ export function makeGetFileToken(execute, cacheRef) {
51
+ return (collection) => Effect.gen(function* () {
52
+ const cache = MutableRef.get(cacheRef);
53
+ const cached = cache.get(collection);
54
+ if (cached !== undefined && Date.now() < cached.expiresAt - 30_000) {
55
+ return cached.token;
56
+ }
57
+ const data = yield* toExecuteEffect(HCR.bodyJson(HCR.post("/api/files/token"), { collection }), execute);
58
+ const newCache = new Map(cache).set(collection, data);
59
+ MutableRef.set(cacheRef, newCache);
60
+ return data.token;
61
+ });
62
+ }
63
+ /**
64
+ * Build the file field client interfaces for a collection.
65
+ * Iterates over the schema's properties, creating `PublicFileFieldClient` or
66
+ * `ProtectedFileFieldClient` instances for each `file`-kind field.
67
+ *
68
+ * @internal Called by `makeCollectionClient` — use `collection.fields` instead.
69
+ */
70
+ export function makeFileFields(collection, schema, baseUrl, execute, cacheRef, makeClientHandler) {
71
+ const result = {};
72
+ const getFileToken = makeGetFileToken(execute, cacheRef);
73
+ for (const [key, prop] of Object.entries(schema.properties)) {
74
+ if (prop["x-kind"] !== "file")
75
+ continue;
76
+ const isProtected = prop["x-protected"] === true;
77
+ if (isProtected) {
78
+ const protectedField = {
79
+ isProtected: true,
80
+ asyncUrl: (id, filename, opts) => makeClientHandler(Effect.gen(function* () {
81
+ const token = yield* getFileToken(collection.name);
82
+ return buildFileUrl(baseUrl, collection.name, id, filename, { token, ...(opts?.thumb !== undefined ? { thumb: opts.thumb } : {}) });
83
+ })),
84
+ url: (id, filename, opts) => buildFileUrl(baseUrl, collection.name, id, filename, { token: opts.token, ...(opts.thumb !== undefined ? { thumb: opts.thumb } : {}) }),
85
+ getToken: () => makeClientHandler(getFileToken(collection.name)),
86
+ };
87
+ result[key] = protectedField;
88
+ }
89
+ else {
90
+ const publicField = {
91
+ isProtected: false,
92
+ url: (id, filename, opts) => buildFileUrl(baseUrl, collection.name, id, filename, opts),
93
+ };
94
+ result[key] = publicField;
95
+ }
96
+ }
97
+ return result;
98
+ }
99
+ //# sourceMappingURL=file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/client/file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ import type { HttpClient, HttpClientRequest } from "@effect/platform";
2
+ import { Effect } from "effect";
3
+ import type { MiraError } from "./errors.js";
4
+ /**
5
+ * A dual-interface handler for dealing with Mira server responses.
6
+ *
7
+ * When `TInput extends void` (query operations: getList, getOne, getFirstOrNone, getFullList):
8
+ * - `.raw()` — returns `Promise<TData>`, throws `MiraError` on failure
9
+ * - `.toEffect()` — returns `Effect.Effect<TData, MiraError, HttpClient.HttpClient>`
10
+ *
11
+ * When `TInput` is provided (mutation operations: create, update, delete):
12
+ * - `.raw(input: TInput)` — returns `Promise<TData>`, throws `MiraError` on failure
13
+ * - `.toEffect(input: TInput)` — returns `Effect.Effect<TData, MiraError, HttpClient.HttpClient>`
14
+ *
15
+ * @example
16
+ * import type { ClientHandler } from "@gettersethya/mira-client"
17
+ *
18
+ * // Query handler: TInput = void
19
+ * const listHandler: ClientHandler<{ items: Post[]; nextCursor: number | null }>
20
+ * const items = await listHandler.raw() // Promise-based
21
+ * const effect = listHandler.toEffect() // Effect-based
22
+ *
23
+ * // Mutation handler: TInput = { title: string }
24
+ * const createHandler: ClientHandler<Post, { title: string }>
25
+ * const post = await createHandler.raw({ title: "hello" })
26
+ * const createEffect = createHandler.toEffect({ title: "hello" })
27
+ *
28
+ * @see makeClientHandler — constructs a ClientHandler from an Effect
29
+ * @see makeMutationHandler — constructs a ClientHandler for mutations
30
+ * @see MiraError — the error thrown by .raw()
31
+ */
32
+ export type ClientHandler<TData, TInput = void> = TInput extends void ? {
33
+ raw(): Promise<TData>;
34
+ toEffect(): Effect.Effect<TData, MiraError, HttpClient.HttpClient>;
35
+ } : {
36
+ raw(input: TInput): Promise<TData>;
37
+ toEffect(input: TInput): Effect.Effect<TData, MiraError, HttpClient.HttpClient>;
38
+ };
39
+ /**
40
+ * Construct a `ClientHandler<T>` (query variant, TInput = void) from an Effect.
41
+ *
42
+ * The Effect is provided `FetchHttpClient.layer` at runtime when `.raw()` is called.
43
+ * The `.toEffect()` method returns the Effect without providing the layer, allowing
44
+ * the caller to supply their own `HttpClient` implementation (e.g., for testing).
45
+ *
46
+ * @param effect - The Effect to wrap. Must produce `T` on success or `MiraError` on failure
47
+ * @returns A ClientHandler with zero-arg .raw() and .toEffect()
48
+ *
49
+ * @example
50
+ * import { Effect } from "effect"
51
+ * import { makeClientHandler } from "@gettersethya/mira-client"
52
+ *
53
+ * const handler = makeClientHandler<{ name: string }>(
54
+ * Effect.succeed({ name: "test" })
55
+ * )
56
+ * const result = await handler.raw() // { name: "test" }
57
+ */
58
+ export declare function makeClientHandler<T>(effect: Effect.Effect<T, MiraError, HttpClient.HttpClient>): ClientHandler<T>;
59
+ /**
60
+ * Construct a `ClientHandler<TData, TInput>` (mutation variant) from a function
61
+ * that accepts input and returns an Effect.
62
+ *
63
+ * The input is provided at call time (`.raw(input)` or `.toEffect(input)`).
64
+ * The Effect is provided `FetchHttpClient.layer` at runtime when `.raw()` is called.
65
+ *
66
+ * @param fn - A function that takes `TInput` and returns an Effect producing `TData`
67
+ * @returns A ClientHandler with input-arg .raw() and .toEffect()
68
+ *
69
+ * @example
70
+ * import { Effect } from "effect"
71
+ * import { makeMutationHandler } from "@gettersethya/mira-client"
72
+ *
73
+ * const handler = makeMutationHandler<{ id: string }, { name: string }>(
74
+ * (input) => Effect.succeed({ id: "1", ...input })
75
+ * )
76
+ * const result = await handler.raw({ name: "test" }) // { id: "1", name: "test" }
77
+ */
78
+ export declare function makeMutationHandler<TData, TInput>(fn: (input: TInput) => Effect.Effect<TData, MiraError, HttpClient.HttpClient>): ClientHandler<TData, TInput>;
79
+ /**
80
+ * The core HTTP execution function used internally by all client handlers.
81
+ * Takes an `HttpClientRequest`, sends it to the server, and returns the
82
+ * deserialized response body.
83
+ *
84
+ * Handles authentication token injection, base URL prefixing, and error mapping
85
+ * (HTTP 4xx/5xx responses become `MiraError` in the Effect error channel).
86
+ *
87
+ * @typeParam T - The expected response body type
88
+ * @param req - The HTTP request to execute (constructed via `HttpClientRequest.*`)
89
+ * @returns Effect that resolves to the parsed response body, or fails with MiraError
90
+ */
91
+ export type ExecuteFn = <T>(req: HttpClientRequest.HttpClientRequest) => Effect.Effect<T, MiraError, HttpClient.HttpClient>;
92
+ //# sourceMappingURL=handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/client/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA;AAErE,OAAO,EAAS,MAAM,EAAgB,MAAM,QAAQ,CAAA;AACpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAE5C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,MAAM,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,IAAI,MAAM,SAAS,IAAI,GACjE;IACE,GAAG,IAAI,OAAO,CAAC,KAAK,CAAC,CAAA;IACrB,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA;CACnE,GACD;IACE,GAAG,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAA;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA;CAChF,CAAA;AAEL;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,GACzD,aAAa,CAAC,CAAC,CAAC,CAWlB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,EAC/C,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,GAC5E,aAAa,CAAC,KAAK,EAAE,MAAM,CAAC,CAY9B;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,EACxB,GAAG,EAAE,iBAAiB,CAAC,iBAAiB,KACrC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,SAAS,EAAE,UAAU,CAAC,UAAU,CAAC,CAAA"}
@@ -0,0 +1,62 @@
1
+ import { FetchHttpClient } from "@effect/platform";
2
+ import { Cause, Effect, Exit, Option } from "effect";
3
+ /**
4
+ * Construct a `ClientHandler<T>` (query variant, TInput = void) from an Effect.
5
+ *
6
+ * The Effect is provided `FetchHttpClient.layer` at runtime when `.raw()` is called.
7
+ * The `.toEffect()` method returns the Effect without providing the layer, allowing
8
+ * the caller to supply their own `HttpClient` implementation (e.g., for testing).
9
+ *
10
+ * @param effect - The Effect to wrap. Must produce `T` on success or `MiraError` on failure
11
+ * @returns A ClientHandler with zero-arg .raw() and .toEffect()
12
+ *
13
+ * @example
14
+ * import { Effect } from "effect"
15
+ * import { makeClientHandler } from "@gettersethya/mira-client"
16
+ *
17
+ * const handler = makeClientHandler<{ name: string }>(
18
+ * Effect.succeed({ name: "test" })
19
+ * )
20
+ * const result = await handler.raw() // { name: "test" }
21
+ */
22
+ export function makeClientHandler(effect) {
23
+ return {
24
+ raw: () => Effect.runPromiseExit(effect.pipe(Effect.provide(FetchHttpClient.layer))).then((exit) => {
25
+ if (Exit.isSuccess(exit))
26
+ return exit.value;
27
+ return Promise.reject(Option.getOrElse(Cause.failureOption(exit.cause), () => Cause.squash(exit.cause)));
28
+ }),
29
+ toEffect: () => effect,
30
+ };
31
+ }
32
+ /**
33
+ * Construct a `ClientHandler<TData, TInput>` (mutation variant) from a function
34
+ * that accepts input and returns an Effect.
35
+ *
36
+ * The input is provided at call time (`.raw(input)` or `.toEffect(input)`).
37
+ * The Effect is provided `FetchHttpClient.layer` at runtime when `.raw()` is called.
38
+ *
39
+ * @param fn - A function that takes `TInput` and returns an Effect producing `TData`
40
+ * @returns A ClientHandler with input-arg .raw() and .toEffect()
41
+ *
42
+ * @example
43
+ * import { Effect } from "effect"
44
+ * import { makeMutationHandler } from "@gettersethya/mira-client"
45
+ *
46
+ * const handler = makeMutationHandler<{ id: string }, { name: string }>(
47
+ * (input) => Effect.succeed({ id: "1", ...input })
48
+ * )
49
+ * const result = await handler.raw({ name: "test" }) // { id: "1", name: "test" }
50
+ */
51
+ export function makeMutationHandler(fn) {
52
+ const handler = {
53
+ raw: (input) => Effect.runPromiseExit(fn(input).pipe(Effect.provide(FetchHttpClient.layer))).then((exit) => {
54
+ if (Exit.isSuccess(exit))
55
+ return exit.value;
56
+ return Promise.reject(Option.getOrElse(Cause.failureOption(exit.cause), () => Cause.squash(exit.cause)));
57
+ }),
58
+ toEffect: (input) => fn(input),
59
+ };
60
+ return handler;
61
+ }
62
+ //# sourceMappingURL=handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler.js","sourceRoot":"","sources":["../../../src/client/handler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAClD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAyCpD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,MAA0D;IAE1D,OAAO;QACL,GAAG,EAAE,GAAG,EAAE,CACR,MAAM,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;YACtF,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAA;YAC3C,OAAO,OAAO,CAAC,MAAM,CACnB,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAClF,CAAA;QACH,CAAC,CAAC;QACJ,QAAQ,EAAE,GAAG,EAAE,CAAC,MAAM;KACvB,CAAA;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CACjC,EAA6E;IAE7E,MAAM,OAAO,GAAG;QACd,GAAG,EAAE,CAAC,KAAa,EAAE,EAAE,CACrB,MAAM,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;YACzF,IAAI,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;gBAAE,OAAO,IAAI,CAAC,KAAK,CAAA;YAC3C,OAAO,OAAO,CAAC,MAAM,CACnB,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAClF,CAAA;QACH,CAAC,CAAC;QACJ,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC;KACvC,CAAA;IACD,OAAO,OAAuC,CAAA;AAChD,CAAC"}
@@ -0,0 +1,11 @@
1
+ export { createMiraClient } from "./factory.js";
2
+ export { makeClientHandler, makeMutationHandler } from "./handler.js";
3
+ export type { ClientHandler, ExecuteFn } from "./handler.js";
4
+ export { MiraError } from "./errors.js";
5
+ export type { InferRecord, InferCreateInput, InferMutationInput, WithExpand, CollectionFileFields, RelationKeys, FileKeys } from "./types.js";
6
+ export type { BrowserAuth, ServerAuth } from "./auth.js";
7
+ export type { PublicFileFieldClient, ProtectedFileFieldClient } from "./file.js";
8
+ export { buildFileUrl } from "./file.js";
9
+ export { makeCollectionClient } from "./collection.js";
10
+ export type { CollectionClient } from "./collection.js";
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AACrE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC5D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,WAAW,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,oBAAoB,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAC7I,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AACxD,YAAY,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAA;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AACtD,YAAY,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA"}
@@ -0,0 +1,6 @@
1
+ export { createMiraClient } from "./factory.js";
2
+ export { makeClientHandler, makeMutationHandler } from "./handler.js";
3
+ export { MiraError } from "./errors.js";
4
+ export { buildFileUrl } from "./file.js";
5
+ export { makeCollectionClient } from "./collection.js";
6
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/client/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAA;AAErE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAIvC,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA"}