@fragno-dev/core 0.1.7 → 0.1.9

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 (183) hide show
  1. package/.turbo/turbo-build.log +131 -64
  2. package/CHANGELOG.md +19 -0
  3. package/dist/api/api.d.ts +38 -2
  4. package/dist/api/api.d.ts.map +1 -0
  5. package/dist/api/api.js +9 -2
  6. package/dist/api/api.js.map +1 -0
  7. package/dist/api/bind-services.d.ts +6 -0
  8. package/dist/api/bind-services.d.ts.map +1 -0
  9. package/dist/api/bind-services.js +20 -0
  10. package/dist/api/bind-services.js.map +1 -0
  11. package/dist/api/error.d.ts +26 -0
  12. package/dist/api/error.d.ts.map +1 -0
  13. package/dist/{api-DngJDcmO.js → api/error.js} +2 -8
  14. package/dist/api/error.js.map +1 -0
  15. package/dist/api/fragment-definition-builder.d.ts +313 -0
  16. package/dist/api/fragment-definition-builder.d.ts.map +1 -0
  17. package/dist/api/fragment-definition-builder.js +326 -0
  18. package/dist/api/fragment-definition-builder.js.map +1 -0
  19. package/dist/api/fragment-instantiator.d.ts +216 -0
  20. package/dist/api/fragment-instantiator.d.ts.map +1 -0
  21. package/dist/api/fragment-instantiator.js +487 -0
  22. package/dist/api/fragment-instantiator.js.map +1 -0
  23. package/dist/api/fragno-response.d.ts +30 -0
  24. package/dist/api/fragno-response.d.ts.map +1 -0
  25. package/dist/api/fragno-response.js +73 -0
  26. package/dist/api/fragno-response.js.map +1 -0
  27. package/dist/api/internal/path.d.ts +50 -0
  28. package/dist/api/internal/path.d.ts.map +1 -0
  29. package/dist/api/internal/path.js +76 -0
  30. package/dist/api/internal/path.js.map +1 -0
  31. package/dist/api/internal/response-stream.d.ts +43 -0
  32. package/dist/api/internal/response-stream.d.ts.map +1 -0
  33. package/dist/api/internal/response-stream.js +81 -0
  34. package/dist/api/internal/response-stream.js.map +1 -0
  35. package/dist/api/internal/route.js +10 -0
  36. package/dist/api/internal/route.js.map +1 -0
  37. package/dist/api/mutable-request-state.d.ts +82 -0
  38. package/dist/api/mutable-request-state.d.ts.map +1 -0
  39. package/dist/api/mutable-request-state.js +97 -0
  40. package/dist/api/mutable-request-state.js.map +1 -0
  41. package/dist/api/request-context-storage.d.ts +42 -0
  42. package/dist/api/request-context-storage.d.ts.map +1 -0
  43. package/dist/api/request-context-storage.js +43 -0
  44. package/dist/api/request-context-storage.js.map +1 -0
  45. package/dist/api/request-input-context.d.ts +89 -0
  46. package/dist/api/request-input-context.d.ts.map +1 -0
  47. package/dist/api/request-input-context.js +118 -0
  48. package/dist/api/request-input-context.js.map +1 -0
  49. package/dist/api/request-middleware.d.ts +50 -0
  50. package/dist/api/request-middleware.d.ts.map +1 -0
  51. package/dist/api/request-middleware.js +83 -0
  52. package/dist/api/request-middleware.js.map +1 -0
  53. package/dist/api/request-output-context.d.ts +41 -0
  54. package/dist/api/request-output-context.d.ts.map +1 -0
  55. package/dist/api/request-output-context.js +119 -0
  56. package/dist/api/request-output-context.js.map +1 -0
  57. package/dist/api/route-handler-input-options.d.ts +21 -0
  58. package/dist/api/route-handler-input-options.d.ts.map +1 -0
  59. package/dist/api/route.d.ts +54 -3
  60. package/dist/api/route.d.ts.map +1 -0
  61. package/dist/api/route.js +29 -2
  62. package/dist/api/route.js.map +1 -0
  63. package/dist/api/shared-types.d.ts +47 -0
  64. package/dist/api/shared-types.d.ts.map +1 -0
  65. package/dist/api/shared-types.js +1 -0
  66. package/dist/client/client-error.d.ts +60 -0
  67. package/dist/client/client-error.d.ts.map +1 -0
  68. package/dist/client/client-error.js +92 -0
  69. package/dist/client/client-error.js.map +1 -0
  70. package/dist/client/client.d.ts +210 -4
  71. package/dist/client/client.d.ts.map +1 -0
  72. package/dist/client/client.js +397 -6
  73. package/dist/client/client.js.map +1 -0
  74. package/dist/client/client.svelte.d.ts +5 -3
  75. package/dist/client/client.svelte.d.ts.map +1 -1
  76. package/dist/client/client.svelte.js +1 -5
  77. package/dist/client/client.svelte.js.map +1 -1
  78. package/dist/client/internal/fetcher-merge.js +36 -0
  79. package/dist/client/internal/fetcher-merge.js.map +1 -0
  80. package/dist/client/internal/ndjson-streaming.js +139 -0
  81. package/dist/client/internal/ndjson-streaming.js.map +1 -0
  82. package/dist/client/react.d.ts +5 -3
  83. package/dist/client/react.d.ts.map +1 -1
  84. package/dist/client/react.js +3 -5
  85. package/dist/client/react.js.map +1 -1
  86. package/dist/client/solid.d.ts +5 -3
  87. package/dist/client/solid.d.ts.map +1 -1
  88. package/dist/client/solid.js +2 -5
  89. package/dist/client/solid.js.map +1 -1
  90. package/dist/client/vanilla.d.ts +5 -3
  91. package/dist/client/vanilla.d.ts.map +1 -1
  92. package/dist/client/vanilla.js +2 -43
  93. package/dist/client/vanilla.js.map +1 -1
  94. package/dist/client/vue.d.ts +5 -3
  95. package/dist/client/vue.d.ts.map +1 -1
  96. package/dist/client/vue.js +1 -5
  97. package/dist/client/vue.js.map +1 -1
  98. package/dist/http/http-status.d.ts +26 -0
  99. package/dist/http/http-status.d.ts.map +1 -0
  100. package/dist/integrations/react-ssr.js +1 -1
  101. package/dist/internal/symbols.d.ts +9 -0
  102. package/dist/internal/symbols.d.ts.map +1 -0
  103. package/dist/internal/symbols.js +10 -0
  104. package/dist/internal/symbols.js.map +1 -0
  105. package/dist/mod-client.d.ts +36 -0
  106. package/dist/mod-client.d.ts.map +1 -0
  107. package/dist/mod-client.js +21 -0
  108. package/dist/mod-client.js.map +1 -0
  109. package/dist/mod.d.ts +7 -4
  110. package/dist/mod.js +4 -6
  111. package/dist/request/request.d.ts +4 -0
  112. package/dist/request/request.js +5 -0
  113. package/dist/test/test.d.ts +62 -35
  114. package/dist/test/test.d.ts.map +1 -1
  115. package/dist/test/test.js +75 -40
  116. package/dist/test/test.js.map +1 -1
  117. package/dist/util/async.js +40 -0
  118. package/dist/util/async.js.map +1 -0
  119. package/dist/util/content-type.js +49 -0
  120. package/dist/util/content-type.js.map +1 -0
  121. package/dist/util/nanostores.js +31 -0
  122. package/dist/util/nanostores.js.map +1 -0
  123. package/dist/{ssr-BByDVfFD.js → util/ssr.js} +2 -2
  124. package/dist/util/ssr.js.map +1 -0
  125. package/dist/util/types-util.d.ts +8 -0
  126. package/dist/util/types-util.d.ts.map +1 -0
  127. package/package.json +19 -12
  128. package/src/api/api.ts +41 -6
  129. package/src/api/bind-services.ts +42 -0
  130. package/src/api/fragment-definition-builder.extend.test.ts +810 -0
  131. package/src/api/fragment-definition-builder.test.ts +499 -0
  132. package/src/api/fragment-definition-builder.ts +1088 -0
  133. package/src/api/fragment-instantiator.test.ts +1488 -0
  134. package/src/api/fragment-instantiator.ts +1053 -0
  135. package/src/api/fragment-services.test.ts +727 -0
  136. package/src/api/request-context-storage.ts +64 -0
  137. package/src/api/request-middleware.test.ts +301 -225
  138. package/src/api/route.test.ts +87 -1
  139. package/src/api/route.ts +345 -24
  140. package/src/api/shared-types.ts +43 -0
  141. package/src/client/client-builder.test.ts +23 -23
  142. package/src/client/client.ssr.test.ts +3 -3
  143. package/src/client/client.svelte.test.ts +15 -15
  144. package/src/client/client.test.ts +22 -22
  145. package/src/client/client.ts +72 -12
  146. package/src/client/internal/fetcher-merge.ts +1 -1
  147. package/src/client/react.test.ts +2 -2
  148. package/src/client/solid.test.ts +2 -2
  149. package/src/client/vanilla.test.ts +2 -2
  150. package/src/client/vue.test.ts +2 -2
  151. package/src/internal/symbols.ts +5 -0
  152. package/src/mod-client.ts +59 -0
  153. package/src/mod.ts +26 -9
  154. package/src/request/request.ts +8 -0
  155. package/src/test/test.test.ts +200 -381
  156. package/src/test/test.ts +190 -117
  157. package/tsdown.config.ts +8 -5
  158. package/dist/api/fragment-builder.d.ts +0 -4
  159. package/dist/api/fragment-builder.js +0 -3
  160. package/dist/api/fragment-instantiation.d.ts +0 -4
  161. package/dist/api/fragment-instantiation.js +0 -6
  162. package/dist/api-BWN97TOr.d.ts +0 -377
  163. package/dist/api-BWN97TOr.d.ts.map +0 -1
  164. package/dist/api-DngJDcmO.js.map +0 -1
  165. package/dist/client-C5LsYHEI.js +0 -782
  166. package/dist/client-C5LsYHEI.js.map +0 -1
  167. package/dist/fragment-builder-DOnCVBqc.js +0 -47
  168. package/dist/fragment-builder-DOnCVBqc.js.map +0 -1
  169. package/dist/fragment-builder-MGr68GNb.d.ts +0 -409
  170. package/dist/fragment-builder-MGr68GNb.d.ts.map +0 -1
  171. package/dist/fragment-instantiation-C4wvwl6V.js +0 -446
  172. package/dist/fragment-instantiation-C4wvwl6V.js.map +0 -1
  173. package/dist/request-output-context-CdIjwmEN.js +0 -320
  174. package/dist/request-output-context-CdIjwmEN.js.map +0 -1
  175. package/dist/route-Bl9Zr1Yv.d.ts +0 -26
  176. package/dist/route-Bl9Zr1Yv.d.ts.map +0 -1
  177. package/dist/route-C5Uryylh.js +0 -21
  178. package/dist/route-C5Uryylh.js.map +0 -1
  179. package/dist/ssr-BByDVfFD.js.map +0 -1
  180. package/src/api/fragment-builder.ts +0 -80
  181. package/src/api/fragment-instantiation.test.ts +0 -460
  182. package/src/api/fragment-instantiation.ts +0 -499
  183. package/src/api/fragment.test.ts +0 -537
@@ -1 +1 @@
1
- {"version":3,"file":"vue.js","names":["ref","pathParams: Record<string, string | ReadableAtom<string>>","queryParams: Record<string, string | ReadableAtom<string>>","computed"],"sources":["../../src/client/vue.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { atom, type ReadableAtom, type Store, type StoreValue } from \"nanostores\";\nimport type { DeepReadonly, Ref, ShallowRef, UnwrapNestedRefs } from \"vue\";\nimport { computed, getCurrentScope, isRef, onScopeDispose, ref, shallowRef, watch } from \"vue\";\nimport type { NonGetHTTPMethod } from \"../api/api\";\nimport {\n isGetHook,\n isMutatorHook,\n type FragnoClientMutatorData,\n type FragnoClientHookData,\n} from \"./client\";\nimport type { FragnoClientError } from \"./client-error\";\nimport type { MaybeExtractPathParamsOrWiden, QueryParamsHint } from \"../api/internal/path\";\nimport type { InferOr } from \"../util/types-util\";\n\nexport type FragnoVueHook<\n _TMethod extends \"GET\",\n TPath extends string,\n TOutputSchema extends StandardSchemaV1,\n TErrorCode extends string,\n TQueryParameters extends string,\n> = (args?: {\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n}) => {\n data: Ref<InferOr<TOutputSchema, undefined>>;\n loading: Ref<boolean>;\n error: Ref<FragnoClientError<TErrorCode[number]> | undefined>;\n};\n\nexport type FragnoVueMutator<\n _TMethod extends NonGetHTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n> = () => {\n mutate: (args: {\n body?: InferOr<TInputSchema, undefined>;\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n }) => Promise<InferOr<TOutputSchema, undefined>>;\n loading: Ref<boolean | undefined>;\n error: Ref<FragnoClientError<TErrorCode[number]> | undefined>;\n data: Ref<InferOr<TOutputSchema, undefined>>;\n};\n\n/**\n * Converts a Vue Ref to a NanoStore Atom.\n *\n * This is used to convert Vue refs to atoms, so that we can use them in the store.\n *\n * @private\n */\nexport function refToAtom<T>(ref: Ref<T>): ReadableAtom<T> {\n const a = atom(ref.value);\n\n watch(ref, (newVal) => {\n a.set(newVal);\n });\n\n // TODO: Do we need to unsubscribe, or is this handled by `onScopeDispose` below?\n\n return a;\n}\n\n// Helper function to create a Vue composable from a GET hook\n// We want 1 store per hook, so on updates to params, we need to update the store instead of creating a new one.\n// Nanostores only works with atoms (or strings), so we need to convert vue refs to atoms.\nfunction createVueHook<\n TMethod extends \"GET\",\n TPath extends string,\n TOutputSchema extends StandardSchemaV1,\n TErrorCode extends string,\n TQueryParameters extends string,\n>(\n hook: FragnoClientHookData<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters>,\n): FragnoVueHook<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters> {\n return ({ path, query } = {}) => {\n const pathParams: Record<string, string | ReadableAtom<string>> = {};\n const queryParams: Record<string, string | ReadableAtom<string>> = {};\n\n for (const [key, value] of Object.entries(path ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n pathParams[key] = isRef(v) ? refToAtom(v) : v;\n }\n\n for (const [key, value] of Object.entries(query ?? {})) {\n // Dunno why the cast is necessary\n const v = value as string | Ref<string> | ReadableAtom<string>;\n queryParams[key] = isRef(v) ? (refToAtom(v) as ReadableAtom<string>) : v;\n }\n\n const store = hook.store({\n path: pathParams as MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>,\n query: queryParams,\n });\n\n const data = ref();\n const loading = ref();\n const error = ref();\n\n const unsubscribe = store.subscribe((updatedStoreValue) => {\n data.value = updatedStoreValue.data;\n loading.value = updatedStoreValue.loading;\n error.value = updatedStoreValue.error;\n });\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n unsubscribe();\n });\n }\n\n return {\n data,\n loading,\n error,\n };\n };\n}\n\n// Helper function to create a Vue mutator from a mutator hook\nfunction createVueMutator<\n TMethod extends NonGetHTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n>(\n hook: FragnoClientMutatorData<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n >,\n): FragnoVueMutator<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> {\n return () => {\n const store = useStore(hook.mutatorStore);\n\n // Create a wrapped mutate function that handles Vue refs\n const mutate = async (args: {\n body?: InferOr<TInputSchema, undefined>;\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n }) => {\n const { body, path, query } = args;\n\n const pathParams: Record<string, string | ReadableAtom<string>> = {};\n const queryParams: Record<string, string | ReadableAtom<string>> = {};\n\n for (const [key, value] of Object.entries(path ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n pathParams[key] = isRef(v) ? v.value : v;\n }\n\n for (const [key, value] of Object.entries(query ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n queryParams[key] = isRef(v) ? v.value : v;\n }\n\n // Call the store's mutate function with normalized params\n return store.value.mutate({\n body,\n path: pathParams as MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>,\n query: queryParams,\n });\n };\n\n // Return the store-like object with Vue reactive refs\n return {\n mutate,\n loading: computed(() => store.value.loading),\n error: computed(() => store.value.error),\n data: computed(() => store.value.data) as Ref<InferOr<TOutputSchema, undefined>>,\n };\n };\n}\n\nexport function useFragno<T extends Record<string, unknown>>(\n clientObj: T,\n): {\n [K in keyof T]: T[K] extends FragnoClientHookData<\n \"GET\",\n infer TPath,\n infer TOutputSchema,\n infer TErrorCode,\n infer TQueryParameters\n >\n ? FragnoVueHook<\"GET\", TPath, TOutputSchema, TErrorCode, TQueryParameters>\n : T[K] extends FragnoClientMutatorData<\n infer M,\n infer TPath,\n infer TInputSchema,\n infer TOutputSchema,\n infer TErrorCode,\n infer TQueryParameters\n >\n ? FragnoVueMutator<M, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>\n : T[K];\n} {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = {} as any;\n\n for (const key in clientObj) {\n if (!Object.prototype.hasOwnProperty.call(clientObj, key)) {\n continue;\n }\n\n const hook = clientObj[key];\n if (isGetHook(hook)) {\n result[key] = createVueHook(hook);\n } else if (isMutatorHook(hook)) {\n result[key] = createVueMutator(hook);\n } else {\n // Pass through non-hook values unchanged\n result[key] = hook;\n }\n }\n\n return result;\n}\n\nexport function useStore<SomeStore extends Store, Value extends StoreValue<SomeStore>>(\n store: SomeStore,\n): DeepReadonly<UnwrapNestedRefs<ShallowRef<Value>>> {\n const state = shallowRef();\n\n const unsubscribe = store.subscribe((value) => {\n state.value = value;\n });\n\n if (getCurrentScope()) {\n onScopeDispose(unsubscribe);\n }\n\n return state;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAuDA,SAAgB,UAAa,OAA8B;CACzD,MAAM,IAAI,KAAKA,MAAI,MAAM;AAEzB,OAAMA,QAAM,WAAW;AACrB,IAAE,IAAI,OAAO;GACb;AAIF,QAAO;;AAMT,SAAS,cAOP,MAC4E;AAC5E,SAAQ,EAAE,MAAM,UAAU,EAAE,KAAK;EAC/B,MAAMC,aAA4D,EAAE;EACpE,MAAMC,cAA6D,EAAE;AAErE,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE,CAAC,EAAE;GACrD,MAAM,IAAI;AACV,cAAW,OAAO,MAAM,EAAE,GAAG,UAAU,EAAE,GAAG;;AAG9C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,EAAE,CAAC,EAAE;GAEtD,MAAM,IAAI;AACV,eAAY,OAAO,MAAM,EAAE,GAAI,UAAU,EAAE,GAA4B;;EAGzE,MAAM,QAAQ,KAAK,MAAM;GACvB,MAAM;GACN,OAAO;GACR,CAAC;EAEF,MAAM,OAAO,KAAK;EAClB,MAAM,UAAU,KAAK;EACrB,MAAM,QAAQ,KAAK;EAEnB,MAAM,cAAc,MAAM,WAAW,sBAAsB;AACzD,QAAK,QAAQ,kBAAkB;AAC/B,WAAQ,QAAQ,kBAAkB;AAClC,SAAM,QAAQ,kBAAkB;IAChC;AAEF,MAAI,iBAAiB,CACnB,sBAAqB;AACnB,gBAAa;IACb;AAGJ,SAAO;GACL;GACA;GACA;GACD;;;AAKL,SAAS,iBAQP,MAQ6F;AAC7F,cAAa;EACX,MAAM,QAAQ,SAAS,KAAK,aAAa;EAGzC,MAAM,SAAS,OAAO,SAIhB;GACJ,MAAM,EAAE,MAAM,MAAM,UAAU;GAE9B,MAAMD,aAA4D,EAAE;GACpE,MAAMC,cAA6D,EAAE;AAErE,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE,CAAC,EAAE;IACrD,MAAM,IAAI;AACV,eAAW,OAAO,MAAM,EAAE,GAAG,EAAE,QAAQ;;AAGzC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,EAAE,CAAC,EAAE;IACtD,MAAM,IAAI;AACV,gBAAY,OAAO,MAAM,EAAE,GAAG,EAAE,QAAQ;;AAI1C,UAAO,MAAM,MAAM,OAAO;IACxB;IACA,MAAM;IACN,OAAO;IACR,CAAC;;AAIJ,SAAO;GACL;GACA,SAASC,iBAAe,MAAM,MAAM,QAAQ;GAC5C,OAAOA,iBAAe,MAAM,MAAM,MAAM;GACxC,MAAMA,iBAAe,MAAM,MAAM,KAAK;GACvC;;;AAIL,SAAgB,UACd,WAoBA;CAEA,MAAM,SAAS,EAAE;AAEjB,MAAK,MAAM,OAAO,WAAW;AAC3B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,WAAW,IAAI,CACvD;EAGF,MAAM,OAAO,UAAU;AACvB,MAAI,UAAU,KAAK,CACjB,QAAO,OAAO,cAAc,KAAK;WACxB,cAAc,KAAK,CAC5B,QAAO,OAAO,iBAAiB,KAAK;MAGpC,QAAO,OAAO;;AAIlB,QAAO;;AAGT,SAAgB,SACd,OACmD;CACnD,MAAM,QAAQ,YAAY;CAE1B,MAAM,cAAc,MAAM,WAAW,UAAU;AAC7C,QAAM,QAAQ;GACd;AAEF,KAAI,iBAAiB,CACnB,gBAAe,YAAY;AAG7B,QAAO"}
1
+ {"version":3,"file":"vue.js","names":["ref","pathParams: Record<string, string | ReadableAtom<string>>","queryParams: Record<string, string | ReadableAtom<string>>","computed"],"sources":["../../src/client/vue.ts"],"sourcesContent":["import type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport { atom, type ReadableAtom, type Store, type StoreValue } from \"nanostores\";\nimport type { DeepReadonly, Ref, ShallowRef, UnwrapNestedRefs } from \"vue\";\nimport { computed, getCurrentScope, isRef, onScopeDispose, ref, shallowRef, watch } from \"vue\";\nimport type { NonGetHTTPMethod } from \"../api/api\";\nimport {\n isGetHook,\n isMutatorHook,\n type FragnoClientMutatorData,\n type FragnoClientHookData,\n} from \"./client\";\nimport type { FragnoClientError } from \"./client-error\";\nimport type { MaybeExtractPathParamsOrWiden, QueryParamsHint } from \"../api/internal/path\";\nimport type { InferOr } from \"../util/types-util\";\n\nexport type FragnoVueHook<\n _TMethod extends \"GET\",\n TPath extends string,\n TOutputSchema extends StandardSchemaV1,\n TErrorCode extends string,\n TQueryParameters extends string,\n> = (args?: {\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n}) => {\n data: Ref<InferOr<TOutputSchema, undefined>>;\n loading: Ref<boolean>;\n error: Ref<FragnoClientError<TErrorCode[number]> | undefined>;\n};\n\nexport type FragnoVueMutator<\n _TMethod extends NonGetHTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n> = () => {\n mutate: (args: {\n body?: InferOr<TInputSchema, undefined>;\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n }) => Promise<InferOr<TOutputSchema, undefined>>;\n loading: Ref<boolean | undefined>;\n error: Ref<FragnoClientError<TErrorCode[number]> | undefined>;\n data: Ref<InferOr<TOutputSchema, undefined>>;\n};\n\n/**\n * Converts a Vue Ref to a NanoStore Atom.\n *\n * This is used to convert Vue refs to atoms, so that we can use them in the store.\n *\n * @private\n */\nexport function refToAtom<T>(ref: Ref<T>): ReadableAtom<T> {\n const a = atom(ref.value);\n\n watch(ref, (newVal) => {\n a.set(newVal);\n });\n\n // TODO: Do we need to unsubscribe, or is this handled by `onScopeDispose` below?\n\n return a;\n}\n\n// Helper function to create a Vue composable from a GET hook\n// We want 1 store per hook, so on updates to params, we need to update the store instead of creating a new one.\n// Nanostores only works with atoms (or strings), so we need to convert vue refs to atoms.\nfunction createVueHook<\n TMethod extends \"GET\",\n TPath extends string,\n TOutputSchema extends StandardSchemaV1,\n TErrorCode extends string,\n TQueryParameters extends string,\n>(\n hook: FragnoClientHookData<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters>,\n): FragnoVueHook<TMethod, TPath, TOutputSchema, TErrorCode, TQueryParameters> {\n return ({ path, query } = {}) => {\n const pathParams: Record<string, string | ReadableAtom<string>> = {};\n const queryParams: Record<string, string | ReadableAtom<string>> = {};\n\n for (const [key, value] of Object.entries(path ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n pathParams[key] = isRef(v) ? refToAtom(v) : v;\n }\n\n for (const [key, value] of Object.entries(query ?? {})) {\n // Dunno why the cast is necessary\n const v = value as string | Ref<string> | ReadableAtom<string>;\n queryParams[key] = isRef(v) ? (refToAtom(v) as ReadableAtom<string>) : v;\n }\n\n const store = hook.store({\n path: pathParams as MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>,\n query: queryParams,\n });\n\n const data = ref();\n const loading = ref();\n const error = ref();\n\n const unsubscribe = store.subscribe((updatedStoreValue) => {\n data.value = updatedStoreValue.data;\n loading.value = updatedStoreValue.loading;\n error.value = updatedStoreValue.error;\n });\n\n if (getCurrentScope()) {\n onScopeDispose(() => {\n unsubscribe();\n });\n }\n\n return {\n data,\n loading,\n error,\n };\n };\n}\n\n// Helper function to create a Vue mutator from a mutator hook\nfunction createVueMutator<\n TMethod extends NonGetHTTPMethod,\n TPath extends string,\n TInputSchema extends StandardSchemaV1 | undefined,\n TOutputSchema extends StandardSchemaV1 | undefined,\n TErrorCode extends string,\n TQueryParameters extends string,\n>(\n hook: FragnoClientMutatorData<\n TMethod,\n TPath,\n TInputSchema,\n TOutputSchema,\n TErrorCode,\n TQueryParameters\n >,\n): FragnoVueMutator<TMethod, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters> {\n return () => {\n const store = useStore(hook.mutatorStore);\n\n // Create a wrapped mutate function that handles Vue refs\n const mutate = async (args: {\n body?: InferOr<TInputSchema, undefined>;\n path?: MaybeExtractPathParamsOrWiden<TPath, string | Ref<string> | ReadableAtom<string>>;\n query?: QueryParamsHint<TQueryParameters, string | Ref<string> | ReadableAtom<string>>;\n }) => {\n const { body, path, query } = args;\n\n const pathParams: Record<string, string | ReadableAtom<string>> = {};\n const queryParams: Record<string, string | ReadableAtom<string>> = {};\n\n for (const [key, value] of Object.entries(path ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n pathParams[key] = isRef(v) ? v.value : v;\n }\n\n for (const [key, value] of Object.entries(query ?? {})) {\n const v = value as string | Ref<string> | ReadableAtom<string>;\n queryParams[key] = isRef(v) ? v.value : v;\n }\n\n // Call the store's mutate function with normalized params\n return store.value.mutate({\n body,\n path: pathParams as MaybeExtractPathParamsOrWiden<TPath, string | ReadableAtom<string>>,\n query: queryParams,\n });\n };\n\n // Return the store-like object with Vue reactive refs\n return {\n mutate,\n loading: computed(() => store.value.loading),\n error: computed(() => store.value.error),\n data: computed(() => store.value.data) as Ref<InferOr<TOutputSchema, undefined>>,\n };\n };\n}\n\nexport function useFragno<T extends Record<string, unknown>>(\n clientObj: T,\n): {\n [K in keyof T]: T[K] extends FragnoClientHookData<\n \"GET\",\n infer TPath,\n infer TOutputSchema,\n infer TErrorCode,\n infer TQueryParameters\n >\n ? FragnoVueHook<\"GET\", TPath, TOutputSchema, TErrorCode, TQueryParameters>\n : T[K] extends FragnoClientMutatorData<\n infer M,\n infer TPath,\n infer TInputSchema,\n infer TOutputSchema,\n infer TErrorCode,\n infer TQueryParameters\n >\n ? FragnoVueMutator<M, TPath, TInputSchema, TOutputSchema, TErrorCode, TQueryParameters>\n : T[K];\n} {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const result = {} as any;\n\n for (const key in clientObj) {\n if (!Object.prototype.hasOwnProperty.call(clientObj, key)) {\n continue;\n }\n\n const hook = clientObj[key];\n if (isGetHook(hook)) {\n result[key] = createVueHook(hook);\n } else if (isMutatorHook(hook)) {\n result[key] = createVueMutator(hook);\n } else {\n // Pass through non-hook values unchanged\n result[key] = hook;\n }\n }\n\n return result;\n}\n\nexport function useStore<SomeStore extends Store, Value extends StoreValue<SomeStore>>(\n store: SomeStore,\n): DeepReadonly<UnwrapNestedRefs<ShallowRef<Value>>> {\n const state = shallowRef();\n\n const unsubscribe = store.subscribe((value) => {\n state.value = value;\n });\n\n if (getCurrentScope()) {\n onScopeDispose(unsubscribe);\n }\n\n return state;\n}\n"],"mappings":";;;;;;;;;;;;AAuDA,SAAgB,UAAa,OAA8B;CACzD,MAAM,IAAI,KAAKA,MAAI,MAAM;AAEzB,OAAMA,QAAM,WAAW;AACrB,IAAE,IAAI,OAAO;GACb;AAIF,QAAO;;AAMT,SAAS,cAOP,MAC4E;AAC5E,SAAQ,EAAE,MAAM,UAAU,EAAE,KAAK;EAC/B,MAAMC,aAA4D,EAAE;EACpE,MAAMC,cAA6D,EAAE;AAErE,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE,CAAC,EAAE;GACrD,MAAM,IAAI;AACV,cAAW,OAAO,MAAM,EAAE,GAAG,UAAU,EAAE,GAAG;;AAG9C,OAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,EAAE,CAAC,EAAE;GAEtD,MAAM,IAAI;AACV,eAAY,OAAO,MAAM,EAAE,GAAI,UAAU,EAAE,GAA4B;;EAGzE,MAAM,QAAQ,KAAK,MAAM;GACvB,MAAM;GACN,OAAO;GACR,CAAC;EAEF,MAAM,OAAO,KAAK;EAClB,MAAM,UAAU,KAAK;EACrB,MAAM,QAAQ,KAAK;EAEnB,MAAM,cAAc,MAAM,WAAW,sBAAsB;AACzD,QAAK,QAAQ,kBAAkB;AAC/B,WAAQ,QAAQ,kBAAkB;AAClC,SAAM,QAAQ,kBAAkB;IAChC;AAEF,MAAI,iBAAiB,CACnB,sBAAqB;AACnB,gBAAa;IACb;AAGJ,SAAO;GACL;GACA;GACA;GACD;;;AAKL,SAAS,iBAQP,MAQ6F;AAC7F,cAAa;EACX,MAAM,QAAQ,SAAS,KAAK,aAAa;EAGzC,MAAM,SAAS,OAAO,SAIhB;GACJ,MAAM,EAAE,MAAM,MAAM,UAAU;GAE9B,MAAMD,aAA4D,EAAE;GACpE,MAAMC,cAA6D,EAAE;AAErE,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,QAAQ,EAAE,CAAC,EAAE;IACrD,MAAM,IAAI;AACV,eAAW,OAAO,MAAM,EAAE,GAAG,EAAE,QAAQ;;AAGzC,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,SAAS,EAAE,CAAC,EAAE;IACtD,MAAM,IAAI;AACV,gBAAY,OAAO,MAAM,EAAE,GAAG,EAAE,QAAQ;;AAI1C,UAAO,MAAM,MAAM,OAAO;IACxB;IACA,MAAM;IACN,OAAO;IACR,CAAC;;AAIJ,SAAO;GACL;GACA,SAASC,iBAAe,MAAM,MAAM,QAAQ;GAC5C,OAAOA,iBAAe,MAAM,MAAM,MAAM;GACxC,MAAMA,iBAAe,MAAM,MAAM,KAAK;GACvC;;;AAIL,SAAgB,UACd,WAoBA;CAEA,MAAM,SAAS,EAAE;AAEjB,MAAK,MAAM,OAAO,WAAW;AAC3B,MAAI,CAAC,OAAO,UAAU,eAAe,KAAK,WAAW,IAAI,CACvD;EAGF,MAAM,OAAO,UAAU;AACvB,MAAI,UAAU,KAAK,CACjB,QAAO,OAAO,cAAc,KAAK;WACxB,cAAc,KAAK,CAC5B,QAAO,OAAO,iBAAiB,KAAK;MAGpC,QAAO,OAAO;;AAIlB,QAAO;;AAGT,SAAgB,SACd,OACmD;CACnD,MAAM,QAAQ,YAAY;CAE1B,MAAM,cAAc,MAAM,WAAW,UAAU;AAC7C,QAAM,QAAQ;GACd;AAEF,KAAI,iBAAiB,CACnB,gBAAe,YAAY;AAG7B,QAAO"}
@@ -0,0 +1,26 @@
1
+ //#region src/http/http-status.d.ts
2
+ /**
3
+ * @module
4
+ * HTTP Status utility.
5
+ *
6
+ * Modified from honojs/hono
7
+ * Original source: https://github.com/honojs/hono/blob/0e3db674ad3f40be215a55a18062dd8e387ce525/src/utils/http-status.ts
8
+ * License: MIT
9
+ * Date obtained: August 28 2025
10
+ * Copyright (c) 2021-present Yusuke Wada and Hono contributors
11
+ *
12
+ */
13
+ type InfoStatusCode = 100 | 101 | 102 | 103;
14
+ type SuccessStatusCode = 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226;
15
+ type DeprecatedStatusCode = 305 | 306;
16
+ type RedirectStatusCode = 300 | 301 | 302 | 303 | 304 | DeprecatedStatusCode | 307 | 308;
17
+ type ClientErrorStatusCode = 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451;
18
+ type ServerErrorStatusCode = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511;
19
+ /**
20
+ * If you want to use an unofficial status, use `UnofficialStatusCode`.
21
+ */
22
+ type StatusCode = InfoStatusCode | SuccessStatusCode | RedirectStatusCode | ClientErrorStatusCode | ServerErrorStatusCode;
23
+ type ContentlessStatusCode = 101 | 204 | 205 | 304;
24
+ //#endregion
25
+ export { ContentlessStatusCode, StatusCode };
26
+ //# sourceMappingURL=http-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-status.d.ts","names":[],"sources":["../../src/http/http-status.ts"],"sourcesContent":[],"mappings":";;AAYA;AACA;AACA;AACA;AACA;AA8BA;AAKA;;;;AAII,KA3CQ,cAAA,GA2CR,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA;AACA,KA3CQ,iBAAA,GA2CR,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA;AAAqB,KA1Cb,oBAAA,GA0Ca,GAAA,GAAA,GAAA;AAEb,KA3CA,kBAAA,GA2CqB,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GAAA,GA3C8B,oBA2C9B,GAAA,GAAA,GAAA,GAAA;KA1CrB,qBAAA;KA8BA,qBAAA;;;;KAKA,UAAA,GACR,iBACA,oBACA,qBACA,wBACA;KAEQ,qBAAA"}
@@ -1,4 +1,4 @@
1
- import { i as getFinalStoreValues, r as cleanStores } from "../ssr-BByDVfFD.js";
1
+ import { cleanStores, getFinalStoreValues } from "../util/ssr.js";
2
2
 
3
3
  //#region src/integrations/react-ssr.ts
4
4
  /**
@@ -0,0 +1,9 @@
1
+ //#region src/internal/symbols.d.ts
2
+ /**
3
+ * Not actually a symbol, since we might be dealing with multiple instances of this code.
4
+ * @internal
5
+ */
6
+ declare const instantiatedFragmentFakeSymbol: "$fragno-instantiated-fragment";
7
+ //#endregion
8
+ export { instantiatedFragmentFakeSymbol };
9
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.d.ts","names":[],"sources":["../../src/internal/symbols.ts"],"sourcesContent":[],"mappings":";;AAIA;;;cAAa"}
@@ -0,0 +1,10 @@
1
+ //#region src/internal/symbols.ts
2
+ /**
3
+ * Not actually a symbol, since we might be dealing with multiple instances of this code.
4
+ * @internal
5
+ */
6
+ const instantiatedFragmentFakeSymbol = "$fragno-instantiated-fragment";
7
+
8
+ //#endregion
9
+ export { instantiatedFragmentFakeSymbol };
10
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","names":[],"sources":["../../src/internal/symbols.ts"],"sourcesContent":["/**\n * Not actually a symbol, since we might be dealing with multiple instances of this code.\n * @internal\n */\nexport const instantiatedFragmentFakeSymbol = \"$fragno-instantiated-fragment\" as const;\n"],"mappings":";;;;;AAIA,MAAa,iCAAiC"}
@@ -0,0 +1,36 @@
1
+ import { FragnoPublicConfig } from "./api/shared-types.js";
2
+ import { FragnoRouteConfig, RequestThisContext } from "./api/api.js";
3
+ import { BoundServices } from "./api/bind-services.js";
4
+ import { RouteFactory, RouteFactoryContext, defineRoute, defineRoutes } from "./api/route.js";
5
+ import { instantiatedFragmentFakeSymbol } from "./internal/symbols.js";
6
+ import { FragmentInstantiationBuilder, FragnoInstantiatedFragment } from "./api/fragment-instantiator.js";
7
+ import { FragmentDefinition, FragmentDefinitionBuilder, ServiceConstructorFn, ServiceContext } from "./api/fragment-definition-builder.js";
8
+
9
+ //#region src/mod-client.d.ts
10
+ declare function defineFragment(_name: string): {
11
+ withDependencies: () => {
12
+ withDependencies: () => {};
13
+ };
14
+ providesBaseService: () => {
15
+ providesBaseService: () => {};
16
+ };
17
+ providesService: () => {
18
+ providesService: () => {};
19
+ };
20
+ withRequestStorage: () => {
21
+ withRequestStorage: () => {};
22
+ };
23
+ withExternalRequestStorage: () => {
24
+ withExternalRequestStorage: () => {};
25
+ };
26
+ withRequestThisContext: () => {
27
+ withRequestThisContext: () => {};
28
+ };
29
+ extend: () => {
30
+ extend: () => {};
31
+ };
32
+ build: () => {};
33
+ };
34
+ //#endregion
35
+ export { type BoundServices, type FragmentDefinition, FragmentDefinitionBuilder, type FragmentInstantiationBuilder, type FragnoInstantiatedFragment, type FragnoPublicConfig, type FragnoRouteConfig, type RequestThisContext, type RouteFactory, type RouteFactoryContext, type ServiceConstructorFn, type ServiceContext, defineFragment, defineRoute, defineRoutes, instantiatedFragmentFakeSymbol };
36
+ //# sourceMappingURL=mod-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod-client.d.ts","names":[],"sources":["../src/mod-client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;iBAyBgB,cAAA;;;;;;;EAAA,eAAA,EAAc,GAAA,GAAA"}
@@ -0,0 +1,21 @@
1
+ import { FragmentDefinitionBuilder } from "./api/fragment-definition-builder.js";
2
+ import { defineRoute, defineRoutes } from "./api/route.js";
3
+ import { instantiatedFragmentFakeSymbol } from "./internal/symbols.js";
4
+
5
+ //#region src/mod-client.ts
6
+ function defineFragment(_name) {
7
+ return {
8
+ withDependencies: () => ({ withDependencies: () => ({}) }),
9
+ providesBaseService: () => ({ providesBaseService: () => ({}) }),
10
+ providesService: () => ({ providesService: () => ({}) }),
11
+ withRequestStorage: () => ({ withRequestStorage: () => ({}) }),
12
+ withExternalRequestStorage: () => ({ withExternalRequestStorage: () => ({}) }),
13
+ withRequestThisContext: () => ({ withRequestThisContext: () => ({}) }),
14
+ extend: () => ({ extend: () => ({}) }),
15
+ build: () => ({})
16
+ };
17
+ }
18
+
19
+ //#endregion
20
+ export { FragmentDefinitionBuilder, defineFragment, defineRoute, defineRoutes, instantiatedFragmentFakeSymbol };
21
+ //# sourceMappingURL=mod-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mod-client.js","names":[],"sources":["../src/mod-client.ts"],"sourcesContent":["// ============================================================================\n// Client-side entry point for @fragno-dev/core\n// This file mirrors mod.ts but provides stub implementations for server-side\n// APIs that are stripped by unplugin-fragno during browser builds.\n// ============================================================================\n\n// ============================================================================\n// Fragment Definition and Instantiation\n// ============================================================================\n\n// Re-export types only\nexport type {\n FragmentDefinition,\n ServiceContext,\n ServiceConstructorFn,\n} from \"./api/fragment-definition-builder\";\n\nexport type {\n FragmentInstantiationBuilder,\n FragnoInstantiatedFragment,\n BoundServices,\n} from \"./api/fragment-instantiator\";\n\n// Stub implementation for defineFragment\n// This is stripped by unplugin-fragno in browser builds\nexport function defineFragment(_name: string) {\n return {\n withDependencies: () => ({ withDependencies: () => ({}) }),\n providesBaseService: () => ({ providesBaseService: () => ({}) }),\n providesService: () => ({ providesService: () => ({}) }),\n withRequestStorage: () => ({ withRequestStorage: () => ({}) }),\n withExternalRequestStorage: () => ({ withExternalRequestStorage: () => ({}) }),\n withRequestThisContext: () => ({ withRequestThisContext: () => ({}) }),\n extend: () => ({ extend: () => ({}) }),\n build: () => ({}),\n };\n}\n\n// Re-export the builder class (for type compatibility)\nexport { FragmentDefinitionBuilder } from \"./api/fragment-definition-builder\";\n\n// ============================================================================\n// Core Configuration\n// ============================================================================\nexport type { FragnoPublicConfig } from \"./api/shared-types\";\n\n// ============================================================================\n// Route Definition\n// ============================================================================\nexport {\n defineRoute,\n defineRoutes,\n type RouteFactory,\n type RouteFactoryContext,\n} from \"./api/route\";\n\nexport type { FragnoRouteConfig, RequestThisContext } from \"./api/api\";\n\nexport { instantiatedFragmentFakeSymbol } from \"./internal/symbols\";\n"],"mappings":";;;;;AAyBA,SAAgB,eAAe,OAAe;AAC5C,QAAO;EACL,yBAAyB,EAAE,yBAAyB,EAAE,GAAG;EACzD,4BAA4B,EAAE,4BAA4B,EAAE,GAAG;EAC/D,wBAAwB,EAAE,wBAAwB,EAAE,GAAG;EACvD,2BAA2B,EAAE,2BAA2B,EAAE,GAAG;EAC7D,mCAAmC,EAAE,mCAAmC,EAAE,GAAG;EAC7E,+BAA+B,EAAE,+BAA+B,EAAE,GAAG;EACrE,eAAe,EAAE,eAAe,EAAE,GAAG;EACrC,cAAc,EAAE;EACjB"}
package/dist/mod.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import { t as FragnoRouteConfig } from "./api-BWN97TOr.js";
2
- import { a as RouteFactoryContext, i as RouteFactory, n as AnyRouteOrFactory, o as defineRoute, r as FlattenRouteFactories, s as defineRoutes } from "./route-Bl9Zr1Yv.js";
3
- import { c as FragnoPublicClientConfig, l as FragnoPublicConfig, n as FragmentDefinition, o as FragnoFragmentSharedConfig, r as defineFragment, s as FragnoInstantiatedFragment, t as FragmentBuilder, u as createFragment } from "./fragment-builder-MGr68GNb.js";
4
- export { type AnyRouteOrFactory, type FlattenRouteFactories, FragmentBuilder, type FragmentDefinition, type FragnoFragmentSharedConfig, type FragnoInstantiatedFragment, type FragnoPublicClientConfig, type FragnoPublicConfig, type FragnoRouteConfig, type RouteFactory, type RouteFactoryContext, createFragment, defineFragment, defineRoute, defineRoutes };
1
+ import { FragnoPublicConfig } from "./api/shared-types.js";
2
+ import { FragnoRouteConfig, RequestThisContext } from "./api/api.js";
3
+ import { BoundServices } from "./api/bind-services.js";
4
+ import { RouteFactory, RouteFactoryContext, defineRoute, defineRoutes } from "./api/route.js";
5
+ import { AnyFragnoInstantiatedFragment, FragmentInstantiationBuilder, FragnoInstantiatedFragment, instantiate } from "./api/fragment-instantiator.js";
6
+ import { ExtractLinkedServices, FragmentDefinition, FragmentDefinitionBuilder, LinkedFragmentCallback, ServiceConstructorFn, ServiceContext, defineFragment } from "./api/fragment-definition-builder.js";
7
+ export { type AnyFragnoInstantiatedFragment, type BoundServices, type ExtractLinkedServices, type FragmentDefinition, FragmentDefinitionBuilder, type FragmentInstantiationBuilder, type FragnoInstantiatedFragment, type FragnoPublicConfig, type FragnoRouteConfig, type LinkedFragmentCallback, type RequestThisContext, type RouteFactory, type RouteFactoryContext, type ServiceConstructorFn, type ServiceContext, defineFragment, defineRoute, defineRoutes, instantiate };
package/dist/mod.js CHANGED
@@ -1,7 +1,5 @@
1
- import { n as defineFragment, t as FragmentBuilder } from "./fragment-builder-DOnCVBqc.js";
2
- import "./api-DngJDcmO.js";
3
- import "./request-output-context-CdIjwmEN.js";
4
- import { n as defineRoutes, t as defineRoute } from "./route-C5Uryylh.js";
5
- import { t as createFragment } from "./fragment-instantiation-C4wvwl6V.js";
1
+ import { FragmentDefinitionBuilder, defineFragment } from "./api/fragment-definition-builder.js";
2
+ import { defineRoute, defineRoutes } from "./api/route.js";
3
+ import { instantiate } from "./api/fragment-instantiator.js";
6
4
 
7
- export { FragmentBuilder, createFragment, defineFragment, defineRoute, defineRoutes };
5
+ export { FragmentDefinitionBuilder, defineFragment, defineRoute, defineRoutes, instantiate };
@@ -0,0 +1,4 @@
1
+ import { RequestInputContext } from "../api/request-input-context.js";
2
+ import { RequestOutputContext } from "../api/request-output-context.js";
3
+ import { FragnoMiddlewareCallback, RequestMiddlewareInputContext, RequestMiddlewareOptions, RequestMiddlewareOutputContext } from "../api/request-middleware.js";
4
+ export { type FragnoMiddlewareCallback, RequestInputContext, RequestMiddlewareInputContext, type RequestMiddlewareOptions, RequestMiddlewareOutputContext, RequestOutputContext };
@@ -0,0 +1,5 @@
1
+ import { RequestInputContext } from "../api/request-input-context.js";
2
+ import { RequestOutputContext } from "../api/request-output-context.js";
3
+ import { RequestMiddlewareInputContext, RequestMiddlewareOutputContext } from "../api/request-middleware.js";
4
+
5
+ export { RequestInputContext, RequestMiddlewareInputContext, RequestMiddlewareOutputContext, RequestOutputContext };
@@ -1,52 +1,82 @@
1
- import { d as InferOrUnknown, n as HTTPMethod, o as RouteHandlerInputOptions, t as FragnoRouteConfig } from "../api-BWN97TOr.js";
2
- import { n as AnyRouteOrFactory, r as FlattenRouteFactories } from "../route-Bl9Zr1Yv.js";
3
- import { b as ExtractRouteByPath, f as FragnoResponse, l as FragnoPublicConfig, n as FragmentDefinition, x as ExtractRoutePath } from "../fragment-builder-MGr68GNb.js";
4
- import { StandardSchemaV1 } from "@standard-schema/spec";
1
+ import { RouteHandlerInputOptions } from "../api/route-handler-input-options.js";
2
+ import { FragnoPublicConfig } from "../api/shared-types.js";
3
+ import { RequestThisContext } from "../api/api.js";
4
+ import { BoundServices } from "../api/bind-services.js";
5
+ import { AnyRouteOrFactory, FlattenRouteFactories } from "../api/route.js";
6
+ import { FragnoResponse } from "../api/fragno-response.js";
7
+ import { FragnoInstantiatedFragment } from "../api/fragment-instantiator.js";
8
+ import { FragmentDefinition, FragmentDefinitionBuilder } from "../api/fragment-definition-builder.js";
5
9
 
6
10
  //#region src/test/test.d.ts
7
-
11
+ type TestBaseServices<TDeps> = {
12
+ /**
13
+ * Access to fragment dependencies for testing purposes.
14
+ */
15
+ deps: TDeps;
16
+ };
8
17
  /**
9
- * Options for creating a test fragment
18
+ * Extension function that adds test utilities to a fragment definition.
19
+ * This adds a `test` service to base services with access to deps.
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const definition = defineFragment("my-fragment")
24
+ * .withDependencies(({ config }) => ({ client: createClient(config) }))
25
+ * .extend(withTestUtils())
26
+ * .build();
27
+ *
28
+ * const fragment = createFragmentForTest(definition, [], { config: {...} });
29
+ * expect(fragment.services.test.deps.client).toBeDefined();
30
+ * ```
10
31
  */
11
- interface CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig> {
12
- config: TConfig;
13
- options?: Partial<TOptions>;
14
- deps?: Partial<TDeps>;
15
- services?: Partial<TServices>;
16
- additionalContext?: Partial<TAdditionalContext>;
17
- }
32
+ declare function withTestUtils(): <TConfig, TOptions extends FragnoPublicConfig, TDeps, TBaseServices, TServices, TServiceDeps, TPrivateServices, TServiceThisContext extends RequestThisContext, THandlerThisContext extends RequestThisContext, TRequestStorage>(builder: FragmentDefinitionBuilder<TConfig, TOptions, TDeps, TBaseServices, TServices, TServiceDeps, TPrivateServices, TServiceThisContext, THandlerThisContext, TRequestStorage>) => FragmentDefinitionBuilder<TConfig, TOptions, TDeps, TBaseServices & TestBaseServices<TDeps>, TServices, TServiceDeps, TPrivateServices, TServiceThisContext, THandlerThisContext, TRequestStorage>;
18
33
  /**
19
- * Fragment test instance with type-safe callRoute method
34
+ * Options for creating a test fragment with the new architecture
20
35
  */
21
- interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, TRoutes extends readonly FragnoRouteConfig<HTTPMethod, string, StandardSchemaV1 | undefined, StandardSchemaV1 | undefined, string, string>[]> {
36
+ interface CreateFragmentForTestOptions<TConfig, TOptions extends FragnoPublicConfig, TServiceDependencies> {
22
37
  config: TConfig;
23
- deps: TDeps;
24
- services: TServices;
25
- additionalContext: TAdditionalContext & TOptions;
26
- callRoute: <const TMethod extends HTTPMethod, const TPath extends ExtractRoutePath<TRoutes, TMethod>>(method: TMethod, path: TPath, inputOptions?: RouteHandlerInputOptions<TPath, ExtractRouteByPath<TRoutes, TPath, TMethod>["inputSchema"]>) => Promise<FragnoResponse<InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>["outputSchema"]>>>>;
38
+ options?: Partial<TOptions>;
39
+ serviceImplementations?: TServiceDependencies;
27
40
  }
28
41
  /**
29
- * Create a fragment instance for testing with optional dependency and service overrides
42
+ * Create a fragment instance for testing with optional service dependency overrides.
43
+ * This uses the new fragment definition and instantiation architecture.
30
44
  *
31
- * @param fragmentBuilder - The fragment builder with definition and required options
45
+ * **Important:** Use `.extend(withTestUtils())` before `.build()` to expose deps via `services.test.deps`.
46
+ *
47
+ * Returns an instantiated fragment with:
48
+ * - `services` - Services (base + named) from the fragment definition
49
+ * - `services.test.deps` - Dependencies (only if you used .extend(withTestUtils()))
50
+ * - `callRoute()` - Type-safe method to call routes directly
51
+ * - `handler()` - Request handler for integration
52
+ * - `inContext()` - Run code within request context
53
+ *
54
+ * @param definition - The fragment definition from builder.build()
32
55
  * @param routesOrFactories - Route configurations or route factories
33
- * @param options - Configuration and optional overrides for deps/services
34
- * @returns A fragment test instance with a type-safe callRoute method
56
+ * @param options - Configuration and optional overrides
57
+ * @returns An instantiated fragment ready for testing
35
58
  *
36
59
  * @example
37
60
  * ```typescript
61
+ * const definition = defineFragment<{ apiKey: string }>("test")
62
+ * .withDependencies(({ config }) => ({ client: createClient(config.apiKey) }))
63
+ * .providesService("api", ({ deps }) => ({ call: () => deps.client.call() }))
64
+ * .extend(withTestUtils()) // <- Add test utilities
65
+ * .build();
66
+ *
38
67
  * const fragment = createFragmentForTest(
39
- * chatnoDefinition,
40
- * [routesFactory],
68
+ * definition,
69
+ * [route1, route2],
41
70
  * {
42
- * config: { openaiApiKey: "test-key" },
43
- * options: { mountRoute: "/api/chatno" },
44
- * services: {
45
- * generateStreamMessages: mockGenerator
46
- * }
71
+ * config: { apiKey: "test-key" },
72
+ * options: { mountRoute: "/api/test" }
47
73
  * }
48
74
  * );
49
75
  *
76
+ * // Access deps via test utilities
77
+ * expect(fragment.services.test.deps.client).toBeDefined();
78
+ * expect(fragment.services.api.call()).toBeDefined();
79
+ *
50
80
  * // Call routes directly by method and path
51
81
  * const response = await fragment.callRoute("POST", "/login", {
52
82
  * body: { username: "test", password: "test123" }
@@ -57,10 +87,7 @@ interface FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext extends
57
87
  * }
58
88
  * ```
59
89
  */
60
- declare function createFragmentForTest<TConfig, TDeps, TServices extends Record<string, unknown>, TAdditionalContext extends Record<string, unknown>, TOptions extends FragnoPublicConfig, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(fragmentBuilder: {
61
- definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;
62
- $requiredOptions: TOptions;
63
- }, routesOrFactories: TRoutesOrFactories, options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>): FragmentForTest<TConfig, TDeps, TServices, TAdditionalContext, TOptions, FlattenRouteFactories<TRoutesOrFactories>>;
90
+ declare function createFragmentForTest<TConfig, TOptions extends FragnoPublicConfig, TDeps, TBaseServices extends Record<string, unknown>, TServices extends Record<string, unknown>, TServiceDependencies, TPrivateServices extends Record<string, unknown>, TServiceThisContext extends RequestThisContext, THandlerThisContext extends RequestThisContext, TRequestStorage, const TRoutesOrFactories extends readonly AnyRouteOrFactory[]>(definition: FragmentDefinition<TConfig, TOptions, TDeps, TBaseServices, TServices, TServiceDependencies, TPrivateServices, TServiceThisContext, THandlerThisContext, TRequestStorage>, routesOrFactories: TRoutesOrFactories, options: CreateFragmentForTestOptions<TConfig, TOptions, TServiceDependencies>): FragnoInstantiatedFragment<FlattenRouteFactories<TRoutesOrFactories>, TDeps, BoundServices<TBaseServices & TServices>, TServiceThisContext, THandlerThisContext, TRequestStorage>;
64
91
  //#endregion
65
- export { CreateFragmentForTestOptions, FragmentForTest, type FragnoResponse, type RouteHandlerInputOptions, createFragmentForTest };
92
+ export { CreateFragmentForTestOptions, type FragnoResponse, type RouteHandlerInputOptions, TestBaseServices, createFragmentForTest, withTestUtils };
66
93
  //# sourceMappingURL=test.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;AA6BqB,UAVJ,4BAUI,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BANQ,MAMR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBALF,kBAKE,CAAA,CAAA;EAAR,MAAA,EAHH,OAGG;EACiB,OAAA,CAAA,EAHlB,OAGkB,CAHV,QAGU,CAAA;EAAR,IAAA,CAAA,EAFb,OAEa,CAFL,KAEK,CAAA;EAAO,QAAA,CAAA,EADhB,OACgB,CADR,SACQ,CAAA;EAMZ,iBAAA,CAAe,EANV,OAMU,CANF,kBAME,CAAA;;;;;AAU5B,UAVa,eAUb,CAAA,OAAA,EAAA,KAAA,EAAA,SAAA,EAAA,2BANyB,MAMzB,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBALe,kBAKf,EAAA,gBAAA,SAJuB,iBAIvB,CAHA,UAGA,EAAA,MAAA,EADA,gBACA,GAAA,SAAA,EAAA,gBAAA,GAAA,SAAA,EAAA,MAAA,EAAA,MAAA,CAAA,EAAA,CAAA,CAAA;EAJuB,MAAA,EASjB,OATiB;EASjB,IAAA,EACF,KADE;EACF,QAAA,EACI,SADJ;EACI,iBAAA,EACS,kBADT,GAC8B,QAD9B;EACS,SAAA,EAAA,CAAA,sBAEK,UAFL,EAAA,oBAGG,gBAHH,CAGoB,OAHpB,EAG6B,OAH7B,CAAA,CAAA,CAAA,MAAA,EAKT,OALS,EAAA,IAAA,EAMX,KANW,EAAA,YAAA,CAAA,EAOF,wBAPE,CAQf,KARe,EASf,kBATe,CASI,OATJ,EASa,KATb,EASoB,OATpB,CAAA,CAAA,aAAA,CAAA,CAAA,EAAA,GAWd,OAXc,CAYjB,cAZiB,CAaf,cAbe,CAaA,WAbA,CAaY,kBAbZ,CAa+B,OAb/B,EAawC,KAbxC,EAa+C,OAb/C,CAAA,CAAA,cAAA,CAAA,CAAA,CAAA,CAAA,CAAA;;;;;;;;;;;;;;;;;;;;;;;AAkDrB;;;;;;;;;;;AAYqB,iBAZL,qBAYK,CAAA,OAAA,EAAA,KAAA,EAAA,kBATD,MASC,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,2BARQ,MAQR,CAAA,MAAA,EAAA,OAAA,CAAA,EAAA,iBAPF,kBAOE,EAAA,iCAAA,SANuB,iBAMvB,EAAA,CAAA,CAAA,eAAA,EAAA;EACmB,UAAA,EAJxB,kBAIwB,CAJL,OAIK,EAJI,KAIJ,EAJW,SAIX,EAJsB,kBAItB,CAAA;EAAS,gBAAA,EAH3B,QAG2B;CAAO,EAAA,iBAAA,EADnC,kBACmC,EAAA,OAAA,EAA7C,4BAA6C,CAAhB,OAAgB,EAAP,KAAO,EAAA,SAAA,EAAW,kBAAX,EAA+B,QAA/B,CAAA,CAAA,EACrD,eADqD,CAEtD,OAFsD,EAGtD,KAHsD,EAItD,SAJsD,EAKtD,kBALsD,EAMtD,QANsD,EAOtD,qBAPsD,CAOhC,kBAPgC,CAAA,CAAA"}
1
+ {"version":3,"file":"test.d.ts","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;KAeY;;AAAZ;AAsBA;EAGqB,IAAA,EArBb,KAqBa;CAMW;;;;;;;;;;;;;;;;AAoB5B,iBA7BY,aAAA,CAAA,CA6BZ,EAAA,CAAA,OAAA,EAAA,iBA1BiB,kBA0BjB,EAAA,KAAA,EAAA,aAAA,EAAA,SAAA,EAAA,YAAA,EAAA,gBAAA,EAAA,4BApB4B,kBAoB5B,EAAA,4BAnB4B,kBAmB5B,EAAA,eAAA,CAAA,CAAA,OAAA,EAhBS,yBAgBT,CAfE,OAeF,EAdE,QAcF,EAbE,KAaF,EAZE,aAYF,EAXE,SAWF,EAVE,YAUF,EATE,gBASF,EARE,mBAQF,EAPE,mBAOF,EANE,eAMF,CAAA,EAAA,GAJC,yBAID,CAHA,OAGA,EAFA,QAEA,EADA,KACA,EAAA,aAAA,GAAgB,gBAAhB,CAAiC,KAAjC,CAAA,EACA,SADA,EAEA,YAFA,EAGA,gBAHA,EAIA,mBAJA,EAKA,mBALA,EAMA,eANA,CAAA;;;;AAEA,UA+Da,4BA/Db,CAAA,OAAA,EAAA,iBAiEe,kBAjEf,EAAA,oBAAA,CAAA,CAAA;EACA,MAAA,EAmEM,OAnEN;EACA,OAAA,CAAA,EAmEQ,OAnER,CAmEgB,QAnEhB,CAAA;EACA,sBAAA,CAAA,EAmEuB,oBAnEvB;;;;AA4DJ;;;;;;;AA2DA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAAgB,gDAEG,iDAEK,2CACJ,wEAEO,qDACG,gDACA,+EAEc,iCAE9B,mBACV,SACA,UACA,OACA,eACA,WACA,sBACA,kBACA,qBACA,qBACA,qCAEiB,6BACV,6BAA6B,SAAS,UAAU,wBACxD,2BACD,sBAAsB,qBACtB,OACA,cAAc,gBAAgB,YAC9B,qBACA,qBACA"}
package/dist/test/test.js CHANGED
@@ -1,31 +1,84 @@
1
- import "../api-DngJDcmO.js";
2
- import "../request-output-context-CdIjwmEN.js";
3
- import "../route-C5Uryylh.js";
4
- import { t as createFragment } from "../fragment-instantiation-C4wvwl6V.js";
1
+ import { FragmentDefinitionBuilder } from "../api/fragment-definition-builder.js";
2
+ import { instantiateFragment } from "../api/fragment-instantiator.js";
5
3
 
6
4
  //#region src/test/test.ts
7
5
  /**
8
- * Create a fragment instance for testing with optional dependency and service overrides
6
+ * Extension function that adds test utilities to a fragment definition.
7
+ * This adds a `test` service to base services with access to deps.
9
8
  *
10
- * @param fragmentBuilder - The fragment builder with definition and required options
9
+ * @example
10
+ * ```typescript
11
+ * const definition = defineFragment("my-fragment")
12
+ * .withDependencies(({ config }) => ({ client: createClient(config) }))
13
+ * .extend(withTestUtils())
14
+ * .build();
15
+ *
16
+ * const fragment = createFragmentForTest(definition, [], { config: {...} });
17
+ * expect(fragment.services.test.deps.client).toBeDefined();
18
+ * ```
19
+ */
20
+ function withTestUtils() {
21
+ return (builder) => {
22
+ const currentBaseDef = builder.build();
23
+ const currentBaseServices = currentBaseDef.baseServices;
24
+ const newBaseServices = (context) => {
25
+ const existingServices = currentBaseServices ? currentBaseServices(context) : {};
26
+ const testUtils = { deps: context.deps };
27
+ return {
28
+ ...existingServices,
29
+ ...testUtils
30
+ };
31
+ };
32
+ return new FragmentDefinitionBuilder(builder.name, {
33
+ dependencies: currentBaseDef.dependencies,
34
+ baseServices: newBaseServices,
35
+ namedServices: currentBaseDef.namedServices,
36
+ privateServices: currentBaseDef.privateServices,
37
+ serviceDependencies: currentBaseDef.serviceDependencies,
38
+ createRequestStorage: currentBaseDef.createRequestStorage,
39
+ createThisContext: currentBaseDef.createThisContext
40
+ });
41
+ };
42
+ }
43
+ /**
44
+ * Create a fragment instance for testing with optional service dependency overrides.
45
+ * This uses the new fragment definition and instantiation architecture.
46
+ *
47
+ * **Important:** Use `.extend(withTestUtils())` before `.build()` to expose deps via `services.test.deps`.
48
+ *
49
+ * Returns an instantiated fragment with:
50
+ * - `services` - Services (base + named) from the fragment definition
51
+ * - `services.test.deps` - Dependencies (only if you used .extend(withTestUtils()))
52
+ * - `callRoute()` - Type-safe method to call routes directly
53
+ * - `handler()` - Request handler for integration
54
+ * - `inContext()` - Run code within request context
55
+ *
56
+ * @param definition - The fragment definition from builder.build()
11
57
  * @param routesOrFactories - Route configurations or route factories
12
- * @param options - Configuration and optional overrides for deps/services
13
- * @returns A fragment test instance with a type-safe callRoute method
58
+ * @param options - Configuration and optional overrides
59
+ * @returns An instantiated fragment ready for testing
14
60
  *
15
61
  * @example
16
62
  * ```typescript
63
+ * const definition = defineFragment<{ apiKey: string }>("test")
64
+ * .withDependencies(({ config }) => ({ client: createClient(config.apiKey) }))
65
+ * .providesService("api", ({ deps }) => ({ call: () => deps.client.call() }))
66
+ * .extend(withTestUtils()) // <- Add test utilities
67
+ * .build();
68
+ *
17
69
  * const fragment = createFragmentForTest(
18
- * chatnoDefinition,
19
- * [routesFactory],
70
+ * definition,
71
+ * [route1, route2],
20
72
  * {
21
- * config: { openaiApiKey: "test-key" },
22
- * options: { mountRoute: "/api/chatno" },
23
- * services: {
24
- * generateStreamMessages: mockGenerator
25
- * }
73
+ * config: { apiKey: "test-key" },
74
+ * options: { mountRoute: "/api/test" }
26
75
  * }
27
76
  * );
28
77
  *
78
+ * // Access deps via test utilities
79
+ * expect(fragment.services.test.deps.client).toBeDefined();
80
+ * expect(fragment.services.api.call()).toBeDefined();
81
+ *
29
82
  * // Call routes directly by method and path
30
83
  * const response = await fragment.callRoute("POST", "/login", {
31
84
  * body: { username: "test", password: "test123" }
@@ -36,32 +89,14 @@ import { t as createFragment } from "../fragment-instantiation-C4wvwl6V.js";
36
89
  * }
37
90
  * ```
38
91
  */
39
- function createFragmentForTest(fragmentBuilder, routesOrFactories, options) {
40
- const { config, options: fragmentOptions = {}, deps: depsOverride, services: servicesOverride, additionalContext: additionalContextOverride } = options;
41
- const definition = fragmentBuilder.definition;
42
- const deps = {
43
- ...definition.dependencies ? definition.dependencies(config, fragmentOptions) : {},
44
- ...depsOverride
45
- };
46
- const services = {
47
- ...definition.services ? definition.services(config, fragmentOptions, deps) : {},
48
- ...servicesOverride
49
- };
50
- const additionalContext = {
51
- ...definition.additionalContext,
52
- ...fragmentOptions,
53
- ...additionalContextOverride
54
- };
55
- const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions);
56
- return {
57
- config,
58
- deps,
59
- services,
60
- additionalContext,
61
- callRoute: (method, path, inputOptions) => fragment.callRoute(method, path, inputOptions)
62
- };
92
+ function createFragmentForTest(definition, routesOrFactories, options) {
93
+ const { config, options: fragmentOptions = {}, serviceImplementations } = options;
94
+ return instantiateFragment(definition, config, routesOrFactories, {
95
+ mountRoute: "/api/test",
96
+ ...fragmentOptions
97
+ }, serviceImplementations);
63
98
  }
64
99
 
65
100
  //#endregion
66
- export { createFragmentForTest };
101
+ export { createFragmentForTest, withTestUtils };
67
102
  //# sourceMappingURL=test.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"test.js","names":[],"sources":["../../src/test/test.ts"],"sourcesContent":["import type { FragmentDefinition } from \"../api/fragment-builder\";\nimport type { FragnoRouteConfig, HTTPMethod } from \"../api/api\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"../api/route\";\nimport type { FragnoPublicConfig } from \"../api/fragment-instantiation\";\nimport { createFragment } from \"../api/fragment-instantiation\";\nimport type { RouteHandlerInputOptions } from \"../api/route-handler-input-options\";\nimport type { ExtractRouteByPath, ExtractRoutePath } from \"../client/client\";\nimport type { InferOrUnknown } from \"../util/types-util\";\nimport type { StandardSchemaV1 } from \"@standard-schema/spec\";\nimport type { FragnoResponse } from \"../api/fragno-response\";\n\n// Re-export for convenience\nexport type { RouteHandlerInputOptions };\n\nexport type { FragnoResponse };\n\n/**\n * Options for creating a test fragment\n */\nexport interface CreateFragmentForTestOptions<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n> {\n config: TConfig;\n options?: Partial<TOptions>;\n deps?: Partial<TDeps>;\n services?: Partial<TServices>;\n additionalContext?: Partial<TAdditionalContext>;\n}\n\n/**\n * Fragment test instance with type-safe callRoute method\n */\nexport interface FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n TRoutes extends readonly FragnoRouteConfig<\n HTTPMethod,\n string,\n StandardSchemaV1 | undefined,\n StandardSchemaV1 | undefined,\n string,\n string\n >[],\n> {\n config: TConfig;\n deps: TDeps;\n services: TServices;\n additionalContext: TAdditionalContext & TOptions;\n callRoute: <\n const TMethod extends HTTPMethod,\n const TPath extends ExtractRoutePath<TRoutes, TMethod>,\n >(\n method: TMethod,\n path: TPath,\n inputOptions?: RouteHandlerInputOptions<\n TPath,\n ExtractRouteByPath<TRoutes, TPath, TMethod>[\"inputSchema\"]\n >,\n ) => Promise<\n FragnoResponse<\n InferOrUnknown<NonNullable<ExtractRouteByPath<TRoutes, TPath, TMethod>[\"outputSchema\"]>>\n >\n >;\n}\n\n/**\n * Create a fragment instance for testing with optional dependency and service overrides\n *\n * @param fragmentBuilder - The fragment builder with definition and required options\n * @param routesOrFactories - Route configurations or route factories\n * @param options - Configuration and optional overrides for deps/services\n * @returns A fragment test instance with a type-safe callRoute method\n *\n * @example\n * ```typescript\n * const fragment = createFragmentForTest(\n * chatnoDefinition,\n * [routesFactory],\n * {\n * config: { openaiApiKey: \"test-key\" },\n * options: { mountRoute: \"/api/chatno\" },\n * services: {\n * generateStreamMessages: mockGenerator\n * }\n * }\n * );\n *\n * // Call routes directly by method and path\n * const response = await fragment.callRoute(\"POST\", \"/login\", {\n * body: { username: \"test\", password: \"test123\" }\n * });\n *\n * if (response.type === 'json') {\n * expect(response.data).toEqual({...});\n * }\n * ```\n */\nexport function createFragmentForTest<\n TConfig,\n TDeps,\n TServices extends Record<string, unknown>,\n TAdditionalContext extends Record<string, unknown>,\n TOptions extends FragnoPublicConfig,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n fragmentBuilder: {\n definition: FragmentDefinition<TConfig, TDeps, TServices, TAdditionalContext>;\n $requiredOptions: TOptions;\n },\n routesOrFactories: TRoutesOrFactories,\n options: CreateFragmentForTestOptions<TConfig, TDeps, TServices, TAdditionalContext, TOptions>,\n): FragmentForTest<\n TConfig,\n TDeps,\n TServices,\n TAdditionalContext,\n TOptions,\n FlattenRouteFactories<TRoutesOrFactories>\n> {\n const {\n config,\n options: fragmentOptions = {} as TOptions,\n deps: depsOverride,\n services: servicesOverride,\n additionalContext: additionalContextOverride,\n } = options;\n\n // Create deps from definition or use empty object\n const definition = fragmentBuilder.definition;\n const baseDeps = definition.dependencies\n ? definition.dependencies(config, fragmentOptions)\n : ({} as TDeps);\n\n // Merge deps with overrides\n const deps = { ...baseDeps, ...depsOverride } as TDeps;\n\n // Create services from definition or use empty object\n const baseServices = definition.services\n ? definition.services(config, fragmentOptions, deps)\n : ({} as TServices);\n\n // Merge services with overrides\n const services = { ...baseServices, ...servicesOverride } as TServices;\n\n // Merge additional context with options\n const additionalContext = {\n ...definition.additionalContext,\n ...fragmentOptions,\n ...additionalContextOverride,\n } as TAdditionalContext & TOptions;\n\n // Create the actual fragment using createFragment\n const fragment = createFragment(fragmentBuilder, config, routesOrFactories, fragmentOptions);\n\n return {\n config,\n deps,\n services,\n additionalContext,\n callRoute: (method, path, inputOptions) => fragment.callRoute(method, path, inputOptions),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwGA,SAAgB,sBAQd,iBAIA,mBACA,SAQA;CACA,MAAM,EACJ,QACA,SAAS,kBAAkB,EAAE,EAC7B,MAAM,cACN,UAAU,kBACV,mBAAmB,8BACjB;CAGJ,MAAM,aAAa,gBAAgB;CAMnC,MAAM,OAAO;EAAE,GALE,WAAW,eACxB,WAAW,aAAa,QAAQ,gBAAgB,GAC/C,EAAE;EAGqB,GAAG;EAAc;CAQ7C,MAAM,WAAW;EAAE,GALE,WAAW,WAC5B,WAAW,SAAS,QAAQ,iBAAiB,KAAK,GACjD,EAAE;EAG6B,GAAG;EAAkB;CAGzD,MAAM,oBAAoB;EACxB,GAAG,WAAW;EACd,GAAG;EACH,GAAG;EACJ;CAGD,MAAM,WAAW,eAAe,iBAAiB,QAAQ,mBAAmB,gBAAgB;AAE5F,QAAO;EACL;EACA;EACA;EACA;EACA,YAAY,QAAQ,MAAM,iBAAiB,SAAS,UAAU,QAAQ,MAAM,aAAa;EAC1F"}
1
+ {"version":3,"file":"test.js","names":["newBaseServices: ServiceConstructorFn<\n TConfig,\n TOptions,\n TDeps,\n TServiceDeps,\n TPrivateServices,\n TBaseServices & TestBaseServices<TDeps>,\n TServiceThisContext\n >","testUtils: TestBaseServices<TDeps>"],"sources":["../../src/test/test.ts"],"sourcesContent":["import type { RequestThisContext } from \"../api/api\";\nimport type { AnyRouteOrFactory, FlattenRouteFactories } from \"../api/route\";\nimport type { FragnoPublicConfig } from \"../api/shared-types\";\nimport {\n FragmentDefinitionBuilder,\n type FragmentDefinition,\n type ServiceConstructorFn,\n} from \"../api/fragment-definition-builder\";\nimport { instantiateFragment, type FragnoInstantiatedFragment } from \"../api/fragment-instantiator\";\nimport type { BoundServices } from \"../api/bind-services\";\n\n// Re-export for convenience\nexport type { RouteHandlerInputOptions } from \"../api/route-handler-input-options\";\nexport type { FragnoResponse } from \"../api/fragno-response\";\n\nexport type TestBaseServices<TDeps> = {\n /**\n * Access to fragment dependencies for testing purposes.\n */\n deps: TDeps;\n};\n\n/**\n * Extension function that adds test utilities to a fragment definition.\n * This adds a `test` service to base services with access to deps.\n *\n * @example\n * ```typescript\n * const definition = defineFragment(\"my-fragment\")\n * .withDependencies(({ config }) => ({ client: createClient(config) }))\n * .extend(withTestUtils())\n * .build();\n *\n * const fragment = createFragmentForTest(definition, [], { config: {...} });\n * expect(fragment.services.test.deps.client).toBeDefined();\n * ```\n */\nexport function withTestUtils() {\n return <\n TConfig,\n TOptions extends FragnoPublicConfig,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDeps,\n TPrivateServices,\n TServiceThisContext extends RequestThisContext,\n THandlerThisContext extends RequestThisContext,\n TRequestStorage,\n >(\n builder: FragmentDefinitionBuilder<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDeps,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage\n >,\n ): FragmentDefinitionBuilder<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices & TestBaseServices<TDeps>,\n TServices,\n TServiceDeps,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage\n > => {\n // Get the current base services factory\n const currentBaseDef = builder.build();\n const currentBaseServices = currentBaseDef.baseServices;\n\n // Create new base services factory that merges test utilities\n const newBaseServices: ServiceConstructorFn<\n TConfig,\n TOptions,\n TDeps,\n TServiceDeps,\n TPrivateServices,\n TBaseServices & TestBaseServices<TDeps>,\n TServiceThisContext\n > = (context) => {\n // Call existing base services if they exist\n const existingServices = currentBaseServices\n ? currentBaseServices(context)\n : ({} as TBaseServices);\n\n // Add test utilities\n const testUtils: TestBaseServices<TDeps> = {\n deps: context.deps,\n };\n\n return {\n ...existingServices,\n ...testUtils,\n } as TBaseServices & TestBaseServices<TDeps>;\n };\n\n // Create new builder with updated base services\n return new FragmentDefinitionBuilder<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices & TestBaseServices<TDeps>,\n TServices,\n TServiceDeps,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage\n >(builder.name, {\n dependencies: currentBaseDef.dependencies,\n baseServices: newBaseServices,\n namedServices: currentBaseDef.namedServices,\n privateServices: currentBaseDef.privateServices,\n serviceDependencies: currentBaseDef.serviceDependencies,\n createRequestStorage: currentBaseDef.createRequestStorage,\n createThisContext: currentBaseDef.createThisContext,\n });\n };\n}\n\n/**\n * Options for creating a test fragment with the new architecture\n */\nexport interface CreateFragmentForTestOptions<\n TConfig,\n TOptions extends FragnoPublicConfig,\n TServiceDependencies,\n> {\n config: TConfig;\n options?: Partial<TOptions>;\n serviceImplementations?: TServiceDependencies;\n}\n\n/**\n * Create a fragment instance for testing with optional service dependency overrides.\n * This uses the new fragment definition and instantiation architecture.\n *\n * **Important:** Use `.extend(withTestUtils())` before `.build()` to expose deps via `services.test.deps`.\n *\n * Returns an instantiated fragment with:\n * - `services` - Services (base + named) from the fragment definition\n * - `services.test.deps` - Dependencies (only if you used .extend(withTestUtils()))\n * - `callRoute()` - Type-safe method to call routes directly\n * - `handler()` - Request handler for integration\n * - `inContext()` - Run code within request context\n *\n * @param definition - The fragment definition from builder.build()\n * @param routesOrFactories - Route configurations or route factories\n * @param options - Configuration and optional overrides\n * @returns An instantiated fragment ready for testing\n *\n * @example\n * ```typescript\n * const definition = defineFragment<{ apiKey: string }>(\"test\")\n * .withDependencies(({ config }) => ({ client: createClient(config.apiKey) }))\n * .providesService(\"api\", ({ deps }) => ({ call: () => deps.client.call() }))\n * .extend(withTestUtils()) // <- Add test utilities\n * .build();\n *\n * const fragment = createFragmentForTest(\n * definition,\n * [route1, route2],\n * {\n * config: { apiKey: \"test-key\" },\n * options: { mountRoute: \"/api/test\" }\n * }\n * );\n *\n * // Access deps via test utilities\n * expect(fragment.services.test.deps.client).toBeDefined();\n * expect(fragment.services.api.call()).toBeDefined();\n *\n * // Call routes directly by method and path\n * const response = await fragment.callRoute(\"POST\", \"/login\", {\n * body: { username: \"test\", password: \"test123\" }\n * });\n *\n * if (response.type === 'json') {\n * expect(response.data).toEqual({...});\n * }\n * ```\n */\nexport function createFragmentForTest<\n TConfig,\n TOptions extends FragnoPublicConfig,\n TDeps,\n TBaseServices extends Record<string, unknown>,\n TServices extends Record<string, unknown>,\n TServiceDependencies,\n TPrivateServices extends Record<string, unknown>,\n TServiceThisContext extends RequestThisContext,\n THandlerThisContext extends RequestThisContext,\n TRequestStorage,\n const TRoutesOrFactories extends readonly AnyRouteOrFactory[],\n>(\n definition: FragmentDefinition<\n TConfig,\n TOptions,\n TDeps,\n TBaseServices,\n TServices,\n TServiceDependencies,\n TPrivateServices,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage\n >,\n routesOrFactories: TRoutesOrFactories,\n options: CreateFragmentForTestOptions<TConfig, TOptions, TServiceDependencies>,\n): FragnoInstantiatedFragment<\n FlattenRouteFactories<TRoutesOrFactories>,\n TDeps,\n BoundServices<TBaseServices & TServices>,\n TServiceThisContext,\n THandlerThisContext,\n TRequestStorage\n> {\n const { config, options: fragmentOptions = {} as TOptions, serviceImplementations } = options;\n\n // Use default mountRoute for testing if not provided\n const fullOptions = {\n mountRoute: \"/api/test\",\n ...fragmentOptions,\n } as TOptions;\n\n // Instantiate and return the fragment directly\n return instantiateFragment(\n definition,\n config,\n routesOrFactories,\n fullOptions,\n serviceImplementations,\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAqCA,SAAgB,gBAAgB;AAC9B,SAYE,YAuBG;EAEH,MAAM,iBAAiB,QAAQ,OAAO;EACtC,MAAM,sBAAsB,eAAe;EAG3C,MAAMA,mBAQD,YAAY;GAEf,MAAM,mBAAmB,sBACrB,oBAAoB,QAAQ,GAC3B,EAAE;GAGP,MAAMC,YAAqC,EACzC,MAAM,QAAQ,MACf;AAED,UAAO;IACL,GAAG;IACH,GAAG;IACJ;;AAIH,SAAO,IAAI,0BAWT,QAAQ,MAAM;GACd,cAAc,eAAe;GAC7B,cAAc;GACd,eAAe,eAAe;GAC9B,iBAAiB,eAAe;GAChC,qBAAqB,eAAe;GACpC,sBAAsB,eAAe;GACrC,mBAAmB,eAAe;GACnC,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkEN,SAAgB,sBAad,YAYA,mBACA,SAQA;CACA,MAAM,EAAE,QAAQ,SAAS,kBAAkB,EAAE,EAAc,2BAA2B;AAStF,QAAO,oBACL,YACA,QACA,mBATkB;EAClB,YAAY;EACZ,GAAG;EACJ,EAQC,uBACD"}
@@ -0,0 +1,40 @@
1
+ //#region src/util/async.ts
2
+ /**
3
+ * Creates an async iterator from a subscribe function that follows the observable pattern.
4
+ *
5
+ * @template T The type of values produced by the store.
6
+ * @param subscribe A function that subscribes to store updates. It receives a callback to be
7
+ * called on each update, and returns an unsubscribe function.
8
+ * @returns An async generator that yields store values as they are produced.
9
+ */
10
+ function createAsyncIteratorFromCallback(subscribe) {
11
+ const queue = [];
12
+ let unsubscribe = null;
13
+ let resolveNext = null;
14
+ const unsubscribeFunc = subscribe((value) => {
15
+ if (resolveNext) {
16
+ resolveNext({
17
+ value,
18
+ done: false
19
+ });
20
+ resolveNext = null;
21
+ } else queue.push(value);
22
+ });
23
+ if (typeof unsubscribeFunc === "function") unsubscribe = unsubscribeFunc;
24
+ return (async function* () {
25
+ try {
26
+ while (true) if (queue.length > 0) yield queue.shift();
27
+ else yield await new Promise((resolve) => {
28
+ resolveNext = (result) => {
29
+ if (!result.done) resolve(result.value);
30
+ };
31
+ });
32
+ } finally {
33
+ if (unsubscribe) unsubscribe();
34
+ }
35
+ })();
36
+ }
37
+
38
+ //#endregion
39
+ export { createAsyncIteratorFromCallback };
40
+ //# sourceMappingURL=async.js.map