@bikefinder/api-client 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/README.md +56 -13
  2. package/dist/admin/generated/client/client.gen.d.ts +3 -0
  3. package/dist/admin/generated/client/client.gen.d.ts.map +1 -0
  4. package/dist/admin/generated/client/client.gen.js +218 -0
  5. package/dist/admin/generated/client/client.gen.js.map +1 -0
  6. package/dist/admin/generated/client/index.d.ts +11 -0
  7. package/dist/admin/generated/client/index.d.ts.map +1 -0
  8. package/dist/admin/generated/client/index.js +7 -0
  9. package/dist/admin/generated/client/index.js.map +1 -0
  10. package/dist/admin/generated/client/types.gen.d.ts +121 -0
  11. package/dist/admin/generated/client/types.gen.d.ts.map +1 -0
  12. package/dist/admin/generated/client/types.gen.js +3 -0
  13. package/dist/admin/generated/client/types.gen.js.map +1 -0
  14. package/dist/admin/generated/client/utils.gen.d.ts +38 -0
  15. package/dist/admin/generated/client/utils.gen.d.ts.map +1 -0
  16. package/dist/admin/generated/client/utils.gen.js +232 -0
  17. package/dist/admin/generated/client/utils.gen.js.map +1 -0
  18. package/dist/admin/generated/client.gen.d.ts +13 -0
  19. package/dist/admin/generated/client.gen.d.ts.map +1 -0
  20. package/dist/admin/generated/client.gen.js +4 -0
  21. package/dist/admin/generated/client.gen.js.map +1 -0
  22. package/dist/admin/generated/core/auth.gen.d.ts +26 -0
  23. package/dist/admin/generated/core/auth.gen.d.ts.map +1 -0
  24. package/dist/admin/generated/core/auth.gen.js +15 -0
  25. package/dist/admin/generated/core/auth.gen.js.map +1 -0
  26. package/dist/admin/generated/core/bodySerializer.gen.d.ts +26 -0
  27. package/dist/admin/generated/core/bodySerializer.gen.d.ts.map +1 -0
  28. package/dist/admin/generated/core/bodySerializer.gen.js +58 -0
  29. package/dist/admin/generated/core/bodySerializer.gen.js.map +1 -0
  30. package/dist/admin/generated/core/params.gen.d.ts +44 -0
  31. package/dist/admin/generated/core/params.gen.d.ts.map +1 -0
  32. package/dist/admin/generated/core/params.gen.js +113 -0
  33. package/dist/admin/generated/core/params.gen.js.map +1 -0
  34. package/dist/admin/generated/core/pathSerializer.gen.d.ts +34 -0
  35. package/dist/admin/generated/core/pathSerializer.gen.d.ts.map +1 -0
  36. package/dist/admin/generated/core/pathSerializer.gen.js +115 -0
  37. package/dist/admin/generated/core/pathSerializer.gen.js.map +1 -0
  38. package/dist/admin/generated/core/queryKeySerializer.gen.d.ts +19 -0
  39. package/dist/admin/generated/core/queryKeySerializer.gen.d.ts.map +1 -0
  40. package/dist/admin/generated/core/queryKeySerializer.gen.js +100 -0
  41. package/dist/admin/generated/core/queryKeySerializer.gen.js.map +1 -0
  42. package/dist/admin/generated/core/serverSentEvents.gen.d.ts +72 -0
  43. package/dist/admin/generated/core/serverSentEvents.gen.d.ts.map +1 -0
  44. package/dist/admin/generated/core/serverSentEvents.gen.js +137 -0
  45. package/dist/admin/generated/core/serverSentEvents.gen.js.map +1 -0
  46. package/dist/admin/generated/core/types.gen.d.ts +84 -0
  47. package/dist/admin/generated/core/types.gen.d.ts.map +1 -0
  48. package/dist/admin/generated/core/types.gen.js +3 -0
  49. package/dist/admin/generated/core/types.gen.js.map +1 -0
  50. package/dist/admin/generated/core/utils.gen.d.ts +20 -0
  51. package/dist/admin/generated/core/utils.gen.d.ts.map +1 -0
  52. package/dist/admin/generated/core/utils.gen.js +88 -0
  53. package/dist/admin/generated/core/utils.gen.js.map +1 -0
  54. package/dist/admin/generated/index.d.ts +3 -0
  55. package/dist/admin/generated/index.d.ts.map +1 -0
  56. package/dist/admin/generated/index.js +3 -0
  57. package/dist/admin/generated/index.js.map +1 -0
  58. package/dist/admin/generated/sdk.gen.d.ts +74 -0
  59. package/dist/admin/generated/sdk.gen.d.ts.map +1 -0
  60. package/dist/admin/generated/sdk.gen.js +417 -0
  61. package/dist/admin/generated/sdk.gen.js.map +1 -0
  62. package/dist/admin/generated/types.gen.d.ts +2087 -0
  63. package/dist/admin/generated/types.gen.d.ts.map +1 -0
  64. package/dist/admin/generated/types.gen.js +3 -0
  65. package/dist/admin/generated/types.gen.js.map +1 -0
  66. package/dist/admin/generated/zod.gen.d.ts +2786 -0
  67. package/dist/admin/generated/zod.gen.d.ts.map +1 -0
  68. package/dist/admin/generated/zod.gen.js +659 -0
  69. package/dist/admin/generated/zod.gen.js.map +1 -0
  70. package/dist/admin/index.d.ts +15 -0
  71. package/dist/admin/index.d.ts.map +1 -0
  72. package/dist/admin/index.js +19 -0
  73. package/dist/admin/index.js.map +1 -0
  74. package/dist/generated/index.d.ts +1 -1
  75. package/dist/generated/index.d.ts.map +1 -1
  76. package/dist/generated/sdk.gen.d.ts +128 -2
  77. package/dist/generated/sdk.gen.d.ts.map +1 -1
  78. package/dist/generated/sdk.gen.js +468 -5
  79. package/dist/generated/sdk.gen.js.map +1 -1
  80. package/dist/generated/types.gen.d.ts +1867 -0
  81. package/dist/generated/types.gen.d.ts.map +1 -1
  82. package/dist/generated/zod.gen.d.ts +1063 -0
  83. package/dist/generated/zod.gen.d.ts.map +1 -1
  84. package/dist/generated/zod.gen.js +801 -0
  85. package/dist/generated/zod.gen.js.map +1 -1
  86. package/dist/http-client.d.ts +1 -1
  87. package/dist/http-client.d.ts.map +1 -1
  88. package/dist/http-client.js +3 -3
  89. package/dist/http-client.js.map +1 -1
  90. package/dist/index.d.ts +2 -0
  91. package/dist/index.d.ts.map +1 -1
  92. package/dist/index.js +3 -2
  93. package/dist/index.js.map +1 -1
  94. package/dist/realtime/watch.d.ts +21 -0
  95. package/dist/realtime/watch.d.ts.map +1 -0
  96. package/dist/realtime/{watch-trackers.js → watch.js} +13 -11
  97. package/dist/realtime/watch.js.map +1 -0
  98. package/package.json +7 -2
  99. package/src/admin/generated/client/client.gen.ts +300 -0
  100. package/src/admin/generated/client/index.ts +27 -0
  101. package/src/admin/generated/client/types.gen.ts +238 -0
  102. package/src/admin/generated/client/utils.gen.ts +337 -0
  103. package/src/admin/generated/client.gen.ts +24 -0
  104. package/src/admin/generated/core/auth.gen.ts +49 -0
  105. package/src/admin/generated/core/bodySerializer.gen.ts +96 -0
  106. package/src/admin/generated/core/params.gen.ts +188 -0
  107. package/src/admin/generated/core/pathSerializer.gen.ts +186 -0
  108. package/src/admin/generated/core/queryKeySerializer.gen.ts +134 -0
  109. package/src/admin/generated/core/serverSentEvents.gen.ts +265 -0
  110. package/src/admin/generated/core/types.gen.ts +126 -0
  111. package/src/admin/generated/core/utils.gen.ts +146 -0
  112. package/src/admin/generated/index.ts +237 -0
  113. package/src/admin/generated/sdk.gen.ts +1066 -0
  114. package/src/admin/generated/types.gen.ts +2418 -0
  115. package/src/admin/generated/zod.gen.ts +805 -0
  116. package/src/admin/index.ts +34 -0
  117. package/src/generated/index.ts +232 -0
  118. package/src/generated/sdk.gen.ts +1072 -11
  119. package/src/generated/types.gen.ts +2215 -42
  120. package/src/generated/zod.gen.ts +968 -14
  121. package/src/http-client.ts +6 -3
  122. package/src/index.ts +10 -2
  123. package/src/realtime/watch.ts +158 -0
  124. package/dist/realtime/watch-trackers.d.ts +0 -10
  125. package/dist/realtime/watch-trackers.d.ts.map +0 -1
  126. package/dist/realtime/watch-trackers.js.map +0 -1
  127. package/src/realtime/watch-trackers.ts +0 -114
@@ -2,9 +2,12 @@ import { createClient } from "./generated/client/index.js";
2
2
  import type { ResolvedClientOptions } from "./options.js";
3
3
  import { createAuthenticatedFetch } from "./transport.js";
4
4
 
5
- export function createHttpClient(options: ResolvedClientOptions) {
5
+ export function createHttpClient(
6
+ options: ResolvedClientOptions,
7
+ clientFactory = createClient,
8
+ ) {
6
9
  const authenticatedFetch = createAuthenticatedFetch(options);
7
- const client = createClient({
10
+ const client = clientFactory({
8
11
  baseUrl: options.baseUrl,
9
12
  redirect: "error",
10
13
  credentials: "omit",
@@ -15,7 +18,7 @@ export function createHttpClient(options: ResolvedClientOptions) {
15
18
  // A per-call fetch would bypass authentication and transport policy.
16
19
  if (requestOptions.fetch !== authenticatedFetch) {
17
20
  throw new TypeError(
18
- "Configure fetch on createBikeFinderClient, not individual requests.",
21
+ "Configure fetch on the client factory, not individual requests.",
19
22
  );
20
23
  }
21
24
  return request;
package/src/index.ts CHANGED
@@ -1,7 +1,11 @@
1
1
  import { resolveClientOptions, type ApiClientOptions } from "./options.js";
2
2
  import { BikeFinderSdk } from "./generated/sdk.gen.js";
3
3
  import { createHttpClient } from "./http-client.js";
4
- import { watchTrackers } from "./realtime/watch-trackers.js";
4
+ import {
5
+ watch,
6
+ type StreamOperation,
7
+ type StreamOptions,
8
+ } from "./realtime/watch.js";
5
9
  import type { TrackerPageQuery } from "./types.js";
6
10
 
7
11
  export { AuthenticationError } from "./errors.js";
@@ -14,8 +18,12 @@ export function createBikeFinderClient(options: ApiClientOptions) {
14
18
  const requests = new BikeFinderSdk({ client: createHttpClient(resolved) });
15
19
  return {
16
20
  requests,
21
+ watch: <TOperation extends StreamOperation>(
22
+ operation: TOperation,
23
+ options: StreamOptions<TOperation>,
24
+ ) => watch(requests, operation, options),
17
25
  watchTrackers: (query: TrackerPageQuery = {}) =>
18
- watchTrackers(requests, query),
26
+ watch(requests, "subscribeTrackers", { query }),
19
27
  };
20
28
  }
21
29
 
@@ -0,0 +1,158 @@
1
+ import {
2
+ defer,
3
+ filter,
4
+ merge,
5
+ Observable,
6
+ ReplaySubject,
7
+ retry,
8
+ share,
9
+ Subject,
10
+ timer,
11
+ } from "rxjs";
12
+ import { ZodError } from "zod";
13
+
14
+ import { AuthenticationError } from "../errors.js";
15
+ import type { BikeFinderSdk } from "../generated/sdk.gen.js";
16
+ import type { Options } from "../generated/sdk.gen.js";
17
+ import { ApiStreamError } from "./api-stream-error.js";
18
+
19
+ type Method = (...args: never[]) => unknown;
20
+ type Result<TSdk, TKey extends keyof TSdk> = TSdk[TKey] extends Method
21
+ ? Awaited<ReturnType<TSdk[TKey]>>
22
+ : never;
23
+
24
+ export type StreamOperation<TSdk = BikeFinderSdk> = {
25
+ [TKey in keyof TSdk]: Result<TSdk, TKey> extends {
26
+ stream: AsyncIterable<unknown>;
27
+ }
28
+ ? TKey
29
+ : never;
30
+ }[keyof TSdk];
31
+
32
+ export type StreamPayload<
33
+ TKey extends StreamOperation<TSdk>,
34
+ TSdk = BikeFinderSdk,
35
+ > =
36
+ Result<TSdk, TKey> extends {
37
+ stream: AsyncIterable<infer TPayload>;
38
+ }
39
+ ? Exclude<TPayload, null>
40
+ : never;
41
+
42
+ export type StreamOptions<
43
+ TKey extends StreamOperation<TSdk>,
44
+ TSdk = BikeFinderSdk,
45
+ > = Pick<
46
+ NonNullable<Parameters<Extract<TSdk[TKey], Method>>[0]>,
47
+ Extract<
48
+ keyof NonNullable<Parameters<Extract<TSdk[TKey], Method>>[0]>,
49
+ "path" | "query"
50
+ >
51
+ >;
52
+ /**
53
+ * Observe complete snapshots of a generated view, shared by this observable's consumers.
54
+ * Unsubscribe when leaving the view or changing accounts. Re-subscribe after an error
55
+ * to start a fresh retry budget. Reconnects never request historical event replay.
56
+ */
57
+ export function watch<TSdk, TOperation extends StreamOperation<TSdk>>(
58
+ requests: TSdk,
59
+ operation: TOperation,
60
+ options: StreamOptions<TOperation, TSdk>,
61
+ ): Observable<StreamPayload<TOperation, TSdk>> {
62
+ // The generated SDK supplies paths, arguments and validators for every stream.
63
+ // This boundary only adds the shared connection lifecycle.
64
+ const invoke = requests[operation] as (
65
+ options: Options,
66
+ ) => Promise<{ stream: AsyncIterable<unknown> }>;
67
+ return defer(() => {
68
+ const disconnected = new Subject<undefined>();
69
+ const snapshots = new Observable<StreamPayload<TOperation, TSdk>>(
70
+ (subscriber) => {
71
+ disconnected.next(undefined);
72
+ const controller = new AbortController();
73
+
74
+ const consume = async () => {
75
+ try {
76
+ const { stream } = await invoke.call(requests, {
77
+ ...options,
78
+ signal: controller.signal,
79
+ headers: { Accept: "text/event-stream" },
80
+ // Each RxJS retry starts a new generated stream with fresh credentials.
81
+ // The generated transport must not reconnect with an old token itself.
82
+ sseMaxRetryAttempts: 1,
83
+ onSseError: (error) => {
84
+ throw error;
85
+ },
86
+ onSseEvent: ({ event, data }) => {
87
+ if (subscriber.closed || event === "keepalive") return;
88
+ // A comment-only frame is a transport heartbeat, not an observation.
89
+ if (event === undefined && data === undefined) return;
90
+ if (event !== "snapshot") throw new ApiStreamError("protocol");
91
+ // The generated transformer validates and normalizes every JSON
92
+ // payload. Non-JSON SSE strings bypass it and are never snapshots.
93
+ if (data === null || typeof data !== "object") {
94
+ throw new ApiStreamError("protocol");
95
+ }
96
+ subscriber.next(data as StreamPayload<TOperation, TSdk>);
97
+ },
98
+ });
99
+ // Event callbacks retain SSE event names; consuming drives the generated
100
+ // async iterator. There is no handwritten URL, query serializer or parser.
101
+ for await (const _ of stream) {
102
+ if (subscriber.closed) break;
103
+ }
104
+ if (!subscriber.closed) throw new ApiStreamError("network");
105
+ } catch (error) {
106
+ if (subscriber.closed) return;
107
+ disconnected.next(undefined);
108
+ subscriber.error(
109
+ error instanceof AuthenticationError ||
110
+ error instanceof ApiStreamError
111
+ ? error
112
+ : new ApiStreamError(
113
+ error instanceof ZodError ? "protocol" : "network",
114
+ ),
115
+ );
116
+ }
117
+ };
118
+ void consume();
119
+ return () => controller.abort();
120
+ },
121
+ );
122
+
123
+ return merge(
124
+ disconnected,
125
+ snapshots.pipe(
126
+ retry({
127
+ count: 8,
128
+ resetOnSuccess: true,
129
+ delay: (error: unknown, attempt) => {
130
+ const retryable =
131
+ error instanceof ApiStreamError &&
132
+ (error.code === "network" ||
133
+ (error.code === "http" &&
134
+ (error.status === 408 ||
135
+ error.status === 429 ||
136
+ (error.status ?? 0) >= 500)));
137
+ if (!retryable) throw error;
138
+ const maximum = Math.min(30_000, 1_000 * 2 ** (attempt - 1));
139
+ return timer(maximum * (0.5 + Math.random() * 0.5));
140
+ },
141
+ }),
142
+ ),
143
+ );
144
+ }).pipe(
145
+ share({
146
+ connector: () =>
147
+ new ReplaySubject<StreamPayload<TOperation, TSdk> | undefined>(1),
148
+ resetOnError: true,
149
+ resetOnComplete: true,
150
+ resetOnRefCountZero: true,
151
+ }),
152
+ // An internal disconnect marker replaces the cached page during backoff, so a
153
+ // new subscriber never receives an old snapshot as though it were current.
154
+ filter(
155
+ (page): page is StreamPayload<TOperation, TSdk> => page !== undefined,
156
+ ),
157
+ );
158
+ }
@@ -1,10 +0,0 @@
1
- import { Observable } from "rxjs";
2
- import type { BikeFinderSdk } from "../generated/sdk.gen.js";
3
- import type { TrackerPage, TrackerPageQuery } from "../types.js";
4
- /**
5
- * Observe complete snapshots of one tracker page, shared by this observable's consumers.
6
- * Unsubscribe when leaving the view or changing accounts. Re-subscribe after an error
7
- * to start a fresh retry budget. Reconnects never request historical event replay.
8
- */
9
- export declare function watchTrackers(requests: BikeFinderSdk, query?: TrackerPageQuery): Observable<TrackerPage>;
10
- //# sourceMappingURL=watch-trackers.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"watch-trackers.d.ts","sourceRoot":"","sources":["../../src/realtime/watch-trackers.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,UAAU,EAMX,MAAM,MAAM,CAAC;AAId,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAE7D,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGjE;;;;GAIG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,aAAa,EACvB,KAAK,GAAE,gBAAqB,GAC3B,UAAU,CAAC,WAAW,CAAC,CAsFzB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"watch-trackers.js","sourceRoot":"","sources":["../../src/realtime/watch-trackers.ts"],"names":[],"mappings":"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"}
@@ -1,114 +0,0 @@
1
- import {
2
- defer,
3
- filter,
4
- merge,
5
- Observable,
6
- ReplaySubject,
7
- retry,
8
- share,
9
- Subject,
10
- timer,
11
- } from "rxjs";
12
- import { ZodError } from "zod";
13
-
14
- import { AuthenticationError } from "../errors.js";
15
- import type { BikeFinderSdk } from "../generated/sdk.gen.js";
16
- import { zTrackerPage } from "../generated/zod.gen.js";
17
- import type { TrackerPage, TrackerPageQuery } from "../types.js";
18
- import { ApiStreamError } from "./api-stream-error.js";
19
-
20
- /**
21
- * Observe complete snapshots of one tracker page, shared by this observable's consumers.
22
- * Unsubscribe when leaving the view or changing accounts. Re-subscribe after an error
23
- * to start a fresh retry budget. Reconnects never request historical event replay.
24
- */
25
- export function watchTrackers(
26
- requests: BikeFinderSdk,
27
- query: TrackerPageQuery = {},
28
- ): Observable<TrackerPage> {
29
- return defer(() => {
30
- const disconnected = new Subject<undefined>();
31
- const snapshots = new Observable<TrackerPage>((subscriber) => {
32
- disconnected.next(undefined);
33
- const controller = new AbortController();
34
-
35
- const consume = async () => {
36
- try {
37
- const { stream } = await requests.subscribeTrackers({
38
- query,
39
- signal: controller.signal,
40
- headers: { Accept: "text/event-stream" },
41
- // Each RxJS retry starts a new generated stream with fresh credentials.
42
- // The generated transport must not reconnect with an old token itself.
43
- sseMaxRetryAttempts: 1,
44
- onSseError: (error) => {
45
- throw error;
46
- },
47
- onSseEvent: ({ event, data }) => {
48
- if (subscriber.closed || event === "keepalive") return;
49
- // A comment-only frame is a transport heartbeat, not an observation.
50
- if (event === undefined && data === undefined) return;
51
- if (event !== "snapshot") throw new ApiStreamError("protocol");
52
- // The generator permits non-JSON SSE data; validate every snapshot
53
- // with its generated schema, including malformed JSON strings.
54
- const result = zTrackerPage.safeParse(data);
55
- if (!result.success) throw new ApiStreamError("protocol");
56
- subscriber.next(result.data);
57
- },
58
- });
59
- // Event callbacks retain SSE event names; consuming drives the generated
60
- // async iterator. There is no handwritten URL, query serializer or parser.
61
- for await (const _ of stream) {
62
- if (subscriber.closed) break;
63
- }
64
- if (!subscriber.closed) throw new ApiStreamError("network");
65
- } catch (error) {
66
- if (subscriber.closed) return;
67
- disconnected.next(undefined);
68
- subscriber.error(
69
- error instanceof AuthenticationError ||
70
- error instanceof ApiStreamError
71
- ? error
72
- : new ApiStreamError(
73
- error instanceof ZodError ? "protocol" : "network",
74
- ),
75
- );
76
- }
77
- };
78
- void consume();
79
- return () => controller.abort();
80
- });
81
-
82
- return merge(
83
- disconnected,
84
- snapshots.pipe(
85
- retry({
86
- count: 8,
87
- resetOnSuccess: true,
88
- delay: (error: unknown, attempt) => {
89
- const retryable =
90
- error instanceof ApiStreamError &&
91
- (error.code === "network" ||
92
- (error.code === "http" &&
93
- (error.status === 408 ||
94
- error.status === 429 ||
95
- (error.status ?? 0) >= 500)));
96
- if (!retryable) throw error;
97
- const maximum = Math.min(30_000, 1_000 * 2 ** (attempt - 1));
98
- return timer(maximum * (0.5 + Math.random() * 0.5));
99
- },
100
- }),
101
- ),
102
- );
103
- }).pipe(
104
- share({
105
- connector: () => new ReplaySubject<TrackerPage | undefined>(1),
106
- resetOnError: true,
107
- resetOnComplete: true,
108
- resetOnRefCountZero: true,
109
- }),
110
- // An internal disconnect marker replaces the cached page during backoff, so a
111
- // new subscriber never receives an old snapshot as though it were current.
112
- filter((page): page is TrackerPage => page !== undefined),
113
- );
114
- }