@orpc/openapi 2.0.0-beta.4 → 2.0.0-beta.40

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 (45) hide show
  1. package/README.md +97 -94
  2. package/dist/adapters/aws-lambda/index.d.mts +26 -0
  3. package/dist/adapters/aws-lambda/index.d.ts +26 -0
  4. package/dist/adapters/aws-lambda/index.mjs +21 -0
  5. package/dist/adapters/fastify/index.d.mts +23 -0
  6. package/dist/adapters/fastify/index.d.ts +23 -0
  7. package/dist/adapters/fastify/index.mjs +21 -0
  8. package/dist/adapters/fetch/index.d.mts +15 -5
  9. package/dist/adapters/fetch/index.d.ts +15 -5
  10. package/dist/adapters/fetch/index.mjs +6 -6
  11. package/dist/adapters/node/index.d.mts +9 -4
  12. package/dist/adapters/node/index.d.ts +9 -4
  13. package/dist/adapters/node/index.mjs +4 -4
  14. package/dist/adapters/standard/index.d.mts +10 -46
  15. package/dist/adapters/standard/index.d.ts +10 -46
  16. package/dist/adapters/standard/index.mjs +6 -6
  17. package/dist/extensions/route.d.mts +5 -7
  18. package/dist/extensions/route.d.ts +5 -7
  19. package/dist/helpers/index.d.mts +9 -1
  20. package/dist/helpers/index.d.ts +9 -1
  21. package/dist/helpers/index.mjs +1 -1
  22. package/dist/index.d.mts +77 -45
  23. package/dist/index.d.ts +77 -45
  24. package/dist/index.mjs +694 -750
  25. package/dist/plugins/index.d.mts +21 -4
  26. package/dist/plugins/index.d.ts +21 -4
  27. package/dist/plugins/index.mjs +7 -2
  28. package/dist/shared/{openapi.DmAa7YPO.mjs → openapi.B1LWf28E.mjs} +134 -64
  29. package/dist/shared/openapi.B2G-HeFn.mjs +123 -0
  30. package/dist/shared/{openapi.B2SK0ZAr.mjs → openapi.BdClbrwk.mjs} +27 -35
  31. package/dist/shared/openapi.BwNINoeN.d.ts +45 -0
  32. package/dist/shared/{openapi.BQzzr4-4.d.ts → openapi.Cem81Zhm.d.mts} +41 -12
  33. package/dist/shared/{openapi.BcEtAxQj.d.mts → openapi.Cem81Zhm.d.ts} +41 -12
  34. package/dist/shared/openapi.DE5kXinS.d.mts +45 -0
  35. package/dist/shared/openapi.DI21x5Yo.d.mts +31 -0
  36. package/dist/shared/openapi.DI21x5Yo.d.ts +31 -0
  37. package/dist/shared/{openapi.C7m7NAmH.d.mts → openapi.Dl3gew1x.d.mts} +16 -5
  38. package/dist/shared/{openapi.C7m7NAmH.d.ts → openapi.Dl3gew1x.d.ts} +16 -5
  39. package/dist/shared/{openapi.7vgmPxca.d.ts → openapi.EM8eC1Xn.d.ts} +12 -9
  40. package/dist/shared/{openapi.CX6Ri5dP.d.mts → openapi.fnBK1ELE.d.mts} +12 -9
  41. package/dist/shared/{openapi.CTlpLuKN.mjs → openapi.wQKxWEI2.mjs} +97 -48
  42. package/package.json +57 -17
  43. package/dist/shared/openapi.Bt87OzTt.mjs +0 -131
  44. package/dist/shared/openapi.CYgMBSUF.d.mts +0 -18
  45. package/dist/shared/openapi.CYgMBSUF.d.ts +0 -18
@@ -2,10 +2,10 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerRoutingInterceptorOptions, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
- import { StandardUrl } from '@standardserver/core';
5
+ import { StandardUrl } from '@standard-server/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.mjs';
8
- import '@hey-api/spec-types';
7
+ import { OpenAPIDocument, OpenAPIVersion } from '../index.mjs';
8
+ import '@openapi-spec/types';
9
9
  import '@orpc/client';
10
10
 
11
11
  type OpenAPIReferenceHandlerPluginProvider = 'scalar' | 'swagger';
@@ -21,7 +21,14 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
21
21
  * A static or dynamic OpenAPI document to serve.
22
22
  * Receives routing interceptor options when provided as a function.
23
23
  */
24
- spec: Value<Promisable<OpenAPIDocument>, [StandardHandlerRoutingInterceptorOptions<T>]>;
24
+ spec: Value<Promisable<OpenAPIDocument<OpenAPIVersion>>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
+ /**
26
+ * Determines whether the docs UI and OpenAPI JSON are allowed to be served for a request.
27
+ * When it resolves to `false`, the request falls through as unmatched,
28
+ * as if the plugin were not installed. Useful for restricting access
29
+ * to authenticated users.
30
+ */
31
+ allow?: Value<Promisable<boolean>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
32
  /**
26
33
  * The URL path at which to serve the OpenAPI JSON.
27
34
  *
@@ -71,9 +78,19 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
78
  */
72
79
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
80
  }
81
+ /**
82
+ * Serves API reference documentation powered by Scalar or Swagger UI,
83
+ * and exposes the OpenAPI specification as JSON.
84
+ *
85
+ * @remarks
86
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
87
+ *
88
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
89
+ */
74
90
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
91
  name: string;
76
92
  private readonly spec;
93
+ private readonly allow;
77
94
  private readonly specPath;
78
95
  private readonly provider;
79
96
  private readonly providerConfig;
@@ -2,10 +2,10 @@ import { Context } from '@orpc/server';
2
2
  import { StandardHandlerPlugin, StandardHandlerRoutingInterceptorOptions, StandardHandlerOptions } from '@orpc/server/standard';
3
3
  import { Value, Promisable } from '@orpc/shared';
4
4
  import { ApiReferenceConfiguration } from '@scalar/api-reference';
5
- import { StandardUrl } from '@standardserver/core';
5
+ import { StandardUrl } from '@standard-server/core';
6
6
  import { SwaggerUIOptions } from 'swagger-ui';
7
- import { a as OpenAPIDocument } from '../shared/openapi.CYgMBSUF.js';
8
- import '@hey-api/spec-types';
7
+ import { OpenAPIDocument, OpenAPIVersion } from '../index.js';
8
+ import '@openapi-spec/types';
9
9
  import '@orpc/client';
10
10
 
11
11
  type OpenAPIReferenceHandlerPluginProvider = 'scalar' | 'swagger';
@@ -21,7 +21,14 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
21
21
  * A static or dynamic OpenAPI document to serve.
22
22
  * Receives routing interceptor options when provided as a function.
23
23
  */
24
- spec: Value<Promisable<OpenAPIDocument>, [StandardHandlerRoutingInterceptorOptions<T>]>;
24
+ spec: Value<Promisable<OpenAPIDocument<OpenAPIVersion>>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
+ /**
26
+ * Determines whether the docs UI and OpenAPI JSON are allowed to be served for a request.
27
+ * When it resolves to `false`, the request falls through as unmatched,
28
+ * as if the plugin were not installed. Useful for restricting access
29
+ * to authenticated users.
30
+ */
31
+ allow?: Value<Promisable<boolean>, [StandardHandlerRoutingInterceptorOptions<T>]>;
25
32
  /**
26
33
  * The URL path at which to serve the OpenAPI JSON.
27
34
  *
@@ -71,9 +78,19 @@ interface OpenAPIReferenceHandlerPluginOptions<T extends Context, TProvider exte
71
78
  */
72
79
  docsHead?: Value<Promisable<string>, [StandardHandlerRoutingInterceptorOptions<T>]>;
73
80
  }
81
+ /**
82
+ * Serves API reference documentation powered by Scalar or Swagger UI,
83
+ * and exposes the OpenAPI specification as JSON.
84
+ *
85
+ * @remarks
86
+ * **Note**: By default, the API reference UI is served from `/` and the OpenAPI specification from `/spec.json`.
87
+ *
88
+ * @see {@link https://orpc.dev/docs/plugins/openapi-reference | OpenAPI Reference Plugin (Swagger/Scalar)}
89
+ */
74
90
  declare class OpenAPIReferenceHandlerPlugin<T extends Context, TProvider extends OpenAPIReferenceHandlerPluginProvider> implements StandardHandlerPlugin<T> {
75
91
  name: string;
76
92
  private readonly spec;
93
+ private readonly allow;
77
94
  private readonly specPath;
78
95
  private readonly provider;
79
96
  private readonly providerConfig;
@@ -1,8 +1,9 @@
1
- import { toArray, matchesHttpPath, mergeHttpPath, getOpenTelemetryConfig, value, stringifyJSON } from '@orpc/shared';
1
+ import { toArray, matchesHttpPath, mergeHttpPath, value, getTracer, stringifyJSON } from '@orpc/shared';
2
2
 
3
3
  class OpenAPIReferenceHandlerPlugin {
4
4
  name = "~openapi-reference";
5
5
  spec;
6
+ allow;
6
7
  specPath;
7
8
  provider;
8
9
  providerConfig;
@@ -13,6 +14,7 @@ class OpenAPIReferenceHandlerPlugin {
13
14
  docsHead;
14
15
  constructor(options) {
15
16
  this.spec = options.spec;
17
+ this.allow = options.allow;
16
18
  this.specPath = options.specPath ?? "/spec.json";
17
19
  this.provider = options.provider ?? "scalar";
18
20
  this.providerConfig = options.providerConfig;
@@ -44,7 +46,10 @@ class OpenAPIReferenceHandlerPlugin {
44
46
  if (!isSpecPath && !isDocsPath) {
45
47
  return result;
46
48
  }
47
- const span = getOpenTelemetryConfig()?.trace.getActiveSpan();
49
+ if (await value(this.allow, routingInterceptorOptions) === false) {
50
+ return result;
51
+ }
52
+ const span = getTracer()?.getActiveSpan();
48
53
  const spec = await value(this.spec, routingInterceptorOptions);
49
54
  if (isSpecPath) {
50
55
  span?.updateName(`${routingInterceptorOptions.request.method} ${routingInterceptorOptions.request.url} (openapi spec)`);
@@ -1,7 +1,7 @@
1
- import { wrapEventIteratorPreservingMeta, toORPCError, isORPCErrorJson, createORPCErrorFromJson } from '@orpc/client';
2
- import { isPlainObject, isAsyncIteratorObject } from '@orpc/shared';
3
- import { ErrorEvent } from '@standardserver/core';
4
- import { B as BracketNotationSerializer } from './openapi.Bt87OzTt.mjs';
1
+ import { wrapAsyncIteratorPreservingEventMeta, toORPCError, isORPCErrorJson, createORPCErrorFromJson } from '@orpc/client';
2
+ import { isPlainObject, NullProtoObj, copyOnWrite, isAsyncIteratorObject } from '@orpc/shared';
3
+ import { ErrorEvent } from '@standard-server/core';
4
+ import { B as BracketNotationSerializer } from './openapi.B2G-HeFn.mjs';
5
5
 
6
6
  const DEFAULT_OPENAPI_METHOD = "POST";
7
7
  const DEFAULT_OPENAPI_SUCCESS_DESCRIPTION = "OK";
@@ -57,15 +57,6 @@ const DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS = {
57
57
  },
58
58
  isTerminal: true
59
59
  },
60
- regexp: {
61
- condition(data) {
62
- return data instanceof RegExp;
63
- },
64
- serialize(data) {
65
- return data.toString();
66
- },
67
- isTerminal: true
68
- },
69
60
  set: {
70
61
  condition(data) {
71
62
  return data instanceof Set;
@@ -84,45 +75,114 @@ const DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS = {
84
75
  }
85
76
  };
86
77
  class OpenAPIJsonSerializer {
87
- handlers;
78
+ inlineBuiltInHandlers;
79
+ handlerEntries;
88
80
  omitUndefinedProperties;
89
81
  constructor(options = {}) {
90
- this.handlers = {
91
- ...DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS,
92
- ...options.handlers
93
- };
94
82
  this.omitUndefinedProperties = options.omitUndefinedProperties !== false;
83
+ const customHandlers = options.handlers;
84
+ if (customHandlers === void 0) {
85
+ this.inlineBuiltInHandlers = true;
86
+ return;
87
+ }
88
+ let inlineBuiltInHandlers = true;
89
+ let handlerEntries = [];
90
+ for (const key of Object.keys(customHandlers)) {
91
+ const handler = customHandlers[key];
92
+ if (inlineBuiltInHandlers && key in DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS) {
93
+ inlineBuiltInHandlers = false;
94
+ break;
95
+ }
96
+ if (handler !== void 0) {
97
+ handlerEntries.push(handler);
98
+ }
99
+ }
100
+ if (!inlineBuiltInHandlers) {
101
+ handlerEntries = [];
102
+ for (const handler of Object.values({ ...DEFAULT_OPEN_API_JSON_SERIALIZER_HANDLERS, ...customHandlers })) {
103
+ if (handler !== void 0) {
104
+ handlerEntries.push(handler);
105
+ }
106
+ }
107
+ }
108
+ this.inlineBuiltInHandlers = inlineBuiltInHandlers;
109
+ this.handlerEntries = handlerEntries;
95
110
  }
96
111
  serialize(data) {
97
- const [json, maps, blobs] = this.serializeValue(data, [], [], []);
112
+ const maps = [];
113
+ const blobs = [];
114
+ const json = this.serializeValue(data, [], maps, blobs);
98
115
  return { json, maps, blobs };
99
116
  }
117
+ /**
118
+ * `segments` is a shared mutable stack (push/pop while walking),
119
+ * so it must be copied before being stored in `maps`.
120
+ */
100
121
  serializeValue(data, segments, maps, blobs) {
101
- for (const key in this.handlers) {
102
- const handler = this.handlers[key];
103
- if (handler && handler.condition(data)) {
104
- const serialized = handler.serialize(data);
105
- if (handler.isTerminal) {
106
- return [serialized, maps, blobs];
122
+ if (this.inlineBuiltInHandlers) {
123
+ switch (typeof data) {
124
+ case "string":
125
+ case "boolean":
126
+ return data;
127
+ case "number":
128
+ return Number.isNaN(data) ? null : data;
129
+ case "undefined":
130
+ return null;
131
+ case "bigint":
132
+ return data.toString();
133
+ case "object": {
134
+ if (data === null) {
135
+ return data;
136
+ }
137
+ if (data instanceof Date) {
138
+ return Number.isNaN(data.getTime()) ? null : data.toISOString();
139
+ }
140
+ if (data instanceof URL) {
141
+ return data.toString();
142
+ }
143
+ if (data instanceof Set) {
144
+ return this.serializeValue(Array.from(data), segments, maps, blobs);
145
+ }
146
+ if (data instanceof Map) {
147
+ return this.serializeValue(Array.from(data.entries()), segments, maps, blobs);
148
+ }
149
+ }
150
+ }
151
+ }
152
+ const handlerEntries = this.handlerEntries;
153
+ if (handlerEntries) {
154
+ for (let i = 0; i < handlerEntries.length; i++) {
155
+ const handler = handlerEntries[i];
156
+ if (handler.condition(data)) {
157
+ const serialized = handler.serialize(data);
158
+ if (handler.isTerminal) {
159
+ if (serialized instanceof Blob) {
160
+ maps.push(segments.slice());
161
+ blobs.push(serialized);
162
+ }
163
+ return serialized;
164
+ }
165
+ return this.serializeValue(serialized, segments, maps, blobs);
107
166
  }
108
- const result = this.serializeValue(serialized, segments, maps, blobs);
109
- return result;
110
167
  }
111
168
  }
112
169
  if (data instanceof Blob) {
113
- maps.push(segments);
170
+ maps.push(segments.slice());
114
171
  blobs.push(data);
115
- return [data, maps, blobs];
172
+ return data;
116
173
  }
117
174
  if (Array.isArray(data)) {
118
- const json = data.map((v, i) => {
119
- return this.serializeValue(v, [...segments, i], maps, blobs)[0];
120
- });
121
- return [json, maps, blobs];
175
+ const json = [];
176
+ for (let i = 0; i < data.length; i++) {
177
+ segments.push(i);
178
+ json.push(this.serializeValue(data[i], segments, maps, blobs));
179
+ segments.pop();
180
+ }
181
+ return json;
122
182
  }
123
183
  if (isPlainObject(data)) {
124
- const json = {};
125
- for (const k in data) {
184
+ const json = new NullProtoObj();
185
+ for (const k of Object.keys(data)) {
126
186
  const v = data[k];
127
187
  if (k === "toJSON" && typeof v === "function") {
128
188
  continue;
@@ -130,29 +190,38 @@ class OpenAPIJsonSerializer {
130
190
  if (v === void 0 && this.omitUndefinedProperties) {
131
191
  continue;
132
192
  }
133
- json[k] = this.serializeValue(v, [...segments, k], maps, blobs)[0];
193
+ segments.push(k);
194
+ json[k] = this.serializeValue(v, segments, maps, blobs);
195
+ segments.pop();
134
196
  }
135
- return [json, maps, blobs];
197
+ return json;
136
198
  }
137
- return [data, maps, blobs];
199
+ return data;
138
200
  }
139
201
  deserialize(serialized) {
140
- const ref = { data: serialized.json };
202
+ const ref = { json: serialized.json };
141
203
  if (serialized.blobs?.length) {
142
- serialized.maps.forEach((segments, i) => {
204
+ for (let i = 0; i < serialized.maps.length; i++) {
205
+ const blob = serialized.blobs[i];
206
+ if (!(blob instanceof Blob)) {
207
+ throw new TypeError(`Invalid OpenAPI serialized data: blob ${i} is not a Blob.`);
208
+ }
209
+ const segments = serialized.maps[i];
210
+ let original = serialized;
143
211
  let currentRef = ref;
144
- let preSegment = "data";
145
- segments.forEach((segment) => {
146
- currentRef = currentRef[preSegment];
147
- preSegment = segment;
212
+ let preSegment = "json";
213
+ for (let j = 0; j < segments.length; j++) {
214
+ original = original[preSegment];
215
+ currentRef = copyOnWrite(currentRef, preSegment, original);
216
+ preSegment = segments[j];
148
217
  if (!Object.hasOwn(currentRef, preSegment)) {
149
- throw new Error(`Security error: Invalid serialized data. Segment "${preSegment}" does not exist.`);
218
+ throw new TypeError(`Invalid OpenAPI serialized data: segment "${preSegment}" does not exist.`);
150
219
  }
151
- });
152
- currentRef[preSegment] = serialized.blobs[i];
153
- });
220
+ }
221
+ currentRef[preSegment] = blob;
222
+ }
154
223
  }
155
- return ref.data;
224
+ return ref.json;
156
225
  }
157
226
  }
158
227
 
@@ -166,34 +235,32 @@ class OpenAPISerializer {
166
235
  this.defaultSerializeOptions = serialize;
167
236
  }
168
237
  serialize(data, options = {}) {
169
- const useFormDataForBlobFields = options.useFormDataForBlobFields ?? this.defaultSerializeOptions?.useFormDataForBlobFields ?? true;
170
- const asFormData = options.asFormData ?? this.defaultSerializeOptions?.asFormData ?? false;
171
238
  if (!options.asFormData) {
172
239
  if (data === void 0 || data instanceof ReadableStream || data instanceof Blob) {
173
240
  return data;
174
241
  }
175
242
  if (isAsyncIteratorObject(data)) {
176
- return wrapEventIteratorPreservingMeta(data, {
243
+ return wrapAsyncIteratorPreservingEventMeta(data, {
177
244
  mapResult: (result) => {
178
245
  if (result.value === void 0) {
179
246
  return result;
180
247
  }
181
- return { done: result.done, value: this.serializeValue(result.value, { asFormData: false, useFormDataForBlobFields: false }) };
248
+ return { done: result.done, value: this.serializeValue(result.value, false, false) };
182
249
  },
183
- mapError: (e) => {
184
- return new ErrorEvent({
185
- data: this.serializeValue(toORPCError(e).toJSON(), { asFormData: false, useFormDataForBlobFields: false }),
186
- cause: e
187
- });
188
- }
250
+ mapError: (e) => new ErrorEvent(
251
+ this.serializeValue(toORPCError(e).toJSON(), false, false),
252
+ { cause: e }
253
+ )
189
254
  });
190
255
  }
191
256
  }
192
- return this.serializeValue(data, { useFormDataForBlobFields, asFormData });
257
+ const useFormDataForBlobFields = options.useFormDataForBlobFields ?? this.defaultSerializeOptions?.useFormDataForBlobFields ?? true;
258
+ const asFormData = options.asFormData ?? this.defaultSerializeOptions?.asFormData ?? false;
259
+ return this.serializeValue(data, useFormDataForBlobFields, asFormData);
193
260
  }
194
- serializeValue(value, options) {
261
+ serializeValue(value, useFormDataForBlobFields, asFormData) {
195
262
  const { json, blobs } = this.jsonSerializer.serialize(value);
196
- if (!options.asFormData && (json instanceof Blob || json === void 0 || !blobs?.length || !options.useFormDataForBlobFields)) {
263
+ if (!asFormData && (json instanceof Blob || json === void 0 || !blobs?.length || !useFormDataForBlobFields)) {
197
264
  return json;
198
265
  }
199
266
  const form = new FormData();
@@ -211,7 +278,7 @@ class OpenAPISerializer {
211
278
  return data;
212
279
  }
213
280
  if (isAsyncIteratorObject(data)) {
214
- return wrapEventIteratorPreservingMeta(data, {
281
+ return wrapAsyncIteratorPreservingEventMeta(data, {
215
282
  mapResult: (result) => {
216
283
  if (result.value === void 0) {
217
284
  return result;
@@ -236,6 +303,9 @@ class OpenAPISerializer {
236
303
  }
237
304
  }
238
305
 
306
+ function isBodylessMethod(method) {
307
+ return method === "GET" || method === "HEAD";
308
+ }
239
309
  const PARAMETER_NAME_REGEX = /^[\w-]+$/;
240
310
  function getDynamicPathParams(path) {
241
311
  if (!path.includes("{")) {
@@ -272,4 +342,4 @@ function getDynamicPathParams(path) {
272
342
  return params;
273
343
  }
274
344
 
275
- export { DEFAULT_OPENAPI_METHOD as D, OpenAPISerializer as O, DEFAULT_OPENAPI_INPUT_STRUCTURE as a, DEFAULT_OPENAPI_OUTPUT_STRUCTURE as b, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION as c, OpenAPIJsonSerializer as d, getDynamicPathParams as g };
345
+ export { DEFAULT_OPENAPI_METHOD as D, OpenAPISerializer as O, DEFAULT_OPENAPI_INPUT_STRUCTURE as a, DEFAULT_OPENAPI_OUTPUT_STRUCTURE as b, DEFAULT_OPENAPI_SUCCESS_DESCRIPTION as c, OpenAPIJsonSerializer as d, getDynamicPathParams as g, isBodylessMethod as i };
@@ -0,0 +1,123 @@
1
+ import { isPlainObject, NullProtoObj, getOwn, setOwn } from '@orpc/shared';
2
+
3
+ class BracketNotationSerializer {
4
+ maxExplicitDeserializingArrayIndex;
5
+ constructor(options = {}) {
6
+ this.maxExplicitDeserializingArrayIndex = options.maxExplicitDeserializingArrayIndex ?? 999;
7
+ }
8
+ serialize(data) {
9
+ const result = [];
10
+ this.internalSerialize(data, "", true, result);
11
+ return result;
12
+ }
13
+ internalSerialize(data, path, isRoot, result) {
14
+ if (Array.isArray(data)) {
15
+ data.forEach((item, i) => {
16
+ this.internalSerialize(item, isRoot ? i.toString() : `${path}[${i}]`, false, result);
17
+ });
18
+ } else if (isPlainObject(data)) {
19
+ for (const key of Object.keys(data)) {
20
+ this.internalSerialize(data[key], isRoot ? key : `${path}[${key}]`, false, result);
21
+ }
22
+ } else {
23
+ result.push([path, data]);
24
+ }
25
+ }
26
+ deserialize(serialized) {
27
+ const arrayPushStyles = /* @__PURE__ */ new WeakSet();
28
+ const root = new NullProtoObj();
29
+ for (const [path, value] of serialized) {
30
+ const segments = this.parsePath(path);
31
+ let currentRef = root;
32
+ let nextSegment = segments[0];
33
+ for (let i = 1; i < segments.length; i++) {
34
+ const segment = segments[i];
35
+ const isLast = i === segments.length - 1;
36
+ const existing = getOwn(currentRef, nextSegment);
37
+ let child = existing;
38
+ if (!Array.isArray(child) && !isPlainObject(child)) {
39
+ child = [];
40
+ }
41
+ if (Array.isArray(child)) {
42
+ const isPushStyle = arrayPushStyles.has(child);
43
+ const canStayArray = segment === "" ? isLast && (isPushStyle || child.length === 0) : internalIsValidArrayIndex(segment, this.maxExplicitDeserializingArrayIndex) && !(isLast && isPushStyle);
44
+ if (!canStayArray) {
45
+ arrayPushStyles.delete(child);
46
+ child = isPushStyle ? internalPushStyleArrayToObject(child) : internalArrayToObject(child);
47
+ }
48
+ }
49
+ if (child !== existing) {
50
+ setOwn(currentRef, nextSegment, child);
51
+ }
52
+ currentRef = child;
53
+ nextSegment = segment;
54
+ }
55
+ if (Array.isArray(currentRef) && nextSegment === "") {
56
+ arrayPushStyles.add(currentRef);
57
+ currentRef.push(value);
58
+ } else if (Object.hasOwn(currentRef, nextSegment)) {
59
+ const current = currentRef[nextSegment];
60
+ if (Array.isArray(current)) {
61
+ current.push(value);
62
+ } else {
63
+ setOwn(currentRef, nextSegment, [current, value]);
64
+ }
65
+ } else {
66
+ setOwn(currentRef, nextSegment, value);
67
+ }
68
+ }
69
+ return root;
70
+ }
71
+ stringifyPath(segments) {
72
+ if (segments.length === 0) {
73
+ return "";
74
+ }
75
+ let result = segments[0].toString();
76
+ for (let i = 1; i < segments.length; i++) {
77
+ result += `[${segments[i]}]`;
78
+ }
79
+ return result;
80
+ }
81
+ parsePath(path) {
82
+ const segments = [];
83
+ let inBrackets = false;
84
+ let currentSegment = "";
85
+ for (let i = 0; i < path.length; i++) {
86
+ const char = path[i];
87
+ const nextChar = path[i + 1];
88
+ if (inBrackets && char === "]" && (nextChar === void 0 || nextChar === "[")) {
89
+ if (nextChar === void 0) {
90
+ inBrackets = false;
91
+ }
92
+ segments.push(currentSegment);
93
+ currentSegment = "";
94
+ i++;
95
+ } else if (segments.length === 0 && char === "[") {
96
+ inBrackets = true;
97
+ segments.push(currentSegment);
98
+ currentSegment = "";
99
+ } else {
100
+ currentSegment += char;
101
+ }
102
+ }
103
+ return inBrackets || segments.length === 0 ? [path] : segments;
104
+ }
105
+ }
106
+ const INTEGER_PATTERN = /^0$|^[1-9]\d*$/;
107
+ function internalIsValidArrayIndex(value, maxIndex) {
108
+ return INTEGER_PATTERN.test(value) && Number(value) <= maxIndex;
109
+ }
110
+ function internalArrayToObject(array) {
111
+ const obj = new NullProtoObj();
112
+ array.forEach((item, i) => {
113
+ obj[i] = item;
114
+ });
115
+ return obj;
116
+ }
117
+ function internalPushStyleArrayToObject(array) {
118
+ const obj = new NullProtoObj();
119
+ obj[""] = array.length === 1 ? array[0] : array;
120
+ return obj;
121
+ }
122
+
123
+ export { BracketNotationSerializer as B };