@legalize-dev/sdk 0.1.0 → 0.2.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.
package/dist/index.d.ts CHANGED
@@ -704,6 +704,47 @@ interface RequestOptions {
704
704
  */
705
705
  idempotencyKey?: string;
706
706
  }
707
+ interface RequestRawOptions extends Omit<RequestOptions, "json"> {
708
+ /**
709
+ * The wire format to negotiate via the `Accept` header. `"xml"` (the
710
+ * default) requests `application/xml`, `"json"` requests
711
+ * `application/json`, and any other value is sent verbatim as the
712
+ * media type (so `format: "text/xml"` works too). Empty falls back to
713
+ * XML.
714
+ */
715
+ format?: string;
716
+ }
717
+ /**
718
+ * A raw, non-JSON-decoded API response.
719
+ *
720
+ * Returned by {@link Legalize.requestRaw} for content negotiation — when
721
+ * you want the body in a specific wire format (e.g. XML) instead of the
722
+ * typed JSON model the resource methods return. The SDK has zero runtime
723
+ * dependencies and ships no XML parser: parse `.text` (or `.content`)
724
+ * with your own library (e.g. `fast-xml-parser`, `@xmldom/xmldom`).
725
+ */
726
+ interface RawResponse {
727
+ /** HTTP status of the response. */
728
+ statusCode: number;
729
+ /** Raw response body as bytes. */
730
+ content: Uint8Array;
731
+ /** The body decoded to a string (server charset, default UTF-8). */
732
+ text: string;
733
+ /** The `Content-Type` header value (e.g. `application/xml; charset=utf-8`). */
734
+ contentType: string;
735
+ /** The response headers as a plain, lower-cased mapping. */
736
+ headers: Record<string, string>;
737
+ /** Parse the body as JSON (handy when `format: "json"`). */
738
+ json(): unknown;
739
+ }
740
+ /**
741
+ * Map a `format` shorthand to an Accept media type.
742
+ *
743
+ * `"xml"` → `application/xml`, `"json"` → `application/json`. Any other
744
+ * value is treated as an explicit media type and sent as-is (so
745
+ * `format: "text/xml"` works too). Empty falls back to XML.
746
+ */
747
+ declare function formatToAccept(format: string | undefined): string;
707
748
  /**
708
749
  * Synchronous-API, promise-based client for the Legalize API.
709
750
  *
@@ -746,11 +787,45 @@ declare class Legalize {
746
787
  * failure.
747
788
  */
748
789
  request<T = unknown>(method: string, path: string, options?: RequestOptions): Promise<T>;
790
+ /**
791
+ * Execute a request and return the raw, non-JSON-decoded body.
792
+ *
793
+ * The escape hatch for content negotiation: the typed resource methods
794
+ * always return JSON models, but `requestRaw` lets you fetch any
795
+ * endpoint in another wire format. `format` controls the `Accept`
796
+ * header — `"xml"` (the default) requests `application/xml`, `"json"`
797
+ * requests `application/json`, and any other value is sent verbatim as
798
+ * the media type.
799
+ *
800
+ * The SDK has zero runtime deps and ships no XML parser — parse the
801
+ * returned `.text` (or `.content`) with your own library.
802
+ *
803
+ * Example:
804
+ *
805
+ * const res = await client.requestRaw("GET", "/api/v1/es/laws/BOE-A-1978-31229");
806
+ * res.contentType; // "application/xml; charset=utf-8"
807
+ * const xmlText = res.text; // already application/xml
808
+ *
809
+ * Throws the same APIError subclasses as {@link request} on a non-2xx
810
+ * response; the error body is in whatever format you negotiated.
811
+ */
812
+ requestRaw(method: string, path: string, options?: RequestRawOptions): Promise<RawResponse>;
749
813
  /** Release any resources held by the client. Kept for API symmetry. */
750
814
  close(): Promise<void>;
751
815
  /** TS 5.2+ `using` / `await using` support. */
752
816
  [Symbol.asyncDispose](): Promise<void>;
753
817
  private buildUrl;
818
+ /**
819
+ * Run the retry loop and return the raw 2xx `Response`.
820
+ *
821
+ * Shared by {@link request} (which then JSON-parses) and
822
+ * {@link requestRaw} (which builds a `RawResponse`). Applies the retry
823
+ * policy on transport errors and retryable statuses, populates
824
+ * `lastResponse` on the failing response before raising, and throws the
825
+ * typed `APIError` hierarchy on a final non-2xx. The returned response
826
+ * body is left unread for the caller to consume.
827
+ */
828
+ private sendWithRetry;
754
829
  private sendOnce;
755
830
  }
756
831
  /**
@@ -1007,6 +1082,6 @@ declare class Webhook {
1007
1082
  * output without extra wiring, so we duplicate the literal here. The test
1008
1083
  * suite asserts they match so drift is caught immediately.
1009
1084
  */
1010
- declare const SDK_VERSION = "0.1.0";
1085
+ declare const SDK_VERSION = "0.2.0";
1011
1086
 
1012
- export { APIConnectionError, APIError, type APIErrorOptions, APITimeoutError, type ApiValidationError, AuthenticationError, type Commit, type CommitsResponse, Countries, type CountryInfo, DEFAULT_API_VERSION, DEFAULT_BACKOFF_FACTOR, DEFAULT_BASE_URL, DEFAULT_INITIAL_DELAY, DEFAULT_MAX_DELAY, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_TOLERANCE_SECONDS, type FetchImpl, ForbiddenError, type HTTPValidationError, IDEMPOTENT_METHODS, InvalidRequestError, type JurisdictionInfo, Jurisdictions, KEY_PREFIX, type LawAtCommitResponse, type LawDetail, type LawFilterOptions, type LawIterOptions, type LawListOptions, type LawMeta, type LawSearchOptions, type LawSearchResult, type LawSort, LawTypes, Laws, Legalize, LegalizeError, type LegalizeOptions, NotFoundError, OffsetIterator, PAGE_MAX, PageIterator, type PaginatedLaws, RETRY_STATUSES, RateLimitError, type Reform, type ReformIterOptions, type ReformListOptions, Reforms, type ReformsResponse, type RequestOptions, RetryPolicy, type RetryPolicyOptions, SDK_VERSION, ServerError, ServiceUnavailableError, Stats, type StatsOptions, type StatsResponse, ValidationError, Webhook, type WebhookCreateOptions, type WebhookDeliveriesOptions, type WebhookDeliveriesPage, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, type WebhookUpdateOptions, WebhookVerificationError, type WebhookVerificationReason, type WebhookVerifyOptions, Webhooks, buildQueryString, defaultUserAgent, parseRetryAfter, resolveApiKey, resolveApiVersion, resolveBaseUrl, sleep };
1087
+ export { APIConnectionError, APIError, type APIErrorOptions, APITimeoutError, type ApiValidationError, AuthenticationError, type Commit, type CommitsResponse, Countries, type CountryInfo, DEFAULT_API_VERSION, DEFAULT_BACKOFF_FACTOR, DEFAULT_BASE_URL, DEFAULT_INITIAL_DELAY, DEFAULT_MAX_DELAY, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_TOLERANCE_SECONDS, type FetchImpl, ForbiddenError, type HTTPValidationError, IDEMPOTENT_METHODS, InvalidRequestError, type JurisdictionInfo, Jurisdictions, KEY_PREFIX, type LawAtCommitResponse, type LawDetail, type LawFilterOptions, type LawIterOptions, type LawListOptions, type LawMeta, type LawSearchOptions, type LawSearchResult, type LawSort, LawTypes, Laws, Legalize, LegalizeError, type LegalizeOptions, NotFoundError, OffsetIterator, PAGE_MAX, PageIterator, type PaginatedLaws, RETRY_STATUSES, RateLimitError, type RawResponse, type Reform, type ReformIterOptions, type ReformListOptions, Reforms, type ReformsResponse, type RequestOptions, type RequestRawOptions, RetryPolicy, type RetryPolicyOptions, SDK_VERSION, ServerError, ServiceUnavailableError, Stats, type StatsOptions, type StatsResponse, ValidationError, Webhook, type WebhookCreateOptions, type WebhookDeliveriesOptions, type WebhookDeliveriesPage, type WebhookDelivery, type WebhookDeliveryStatus, type WebhookEndpoint, type WebhookEndpointCreate, type WebhookEndpointUpdate, type WebhookEvent, type WebhookUpdateOptions, WebhookVerificationError, type WebhookVerificationReason, type WebhookVerifyOptions, Webhooks, buildQueryString, defaultUserAgent, formatToAccept, parseRetryAfter, resolveApiKey, resolveApiVersion, resolveBaseUrl, sleep };
package/dist/index.js CHANGED
@@ -722,7 +722,7 @@ var Webhooks = class {
722
722
  };
723
723
 
724
724
  // src/version.ts
725
- var SDK_VERSION = "0.1.0";
725
+ var SDK_VERSION = "0.2.0";
726
726
 
727
727
  // src/client.ts
728
728
  function defaultUserAgent() {
@@ -733,6 +733,22 @@ function stripTrailingSlashes(s) {
733
733
  while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
734
734
  return s.slice(0, end);
735
735
  }
736
+ var FORMAT_ALIASES = {
737
+ xml: "application/xml",
738
+ json: "application/json"
739
+ };
740
+ function formatToAccept(format) {
741
+ const key = (format ?? "").trim().toLowerCase();
742
+ if (!key) return "application/xml";
743
+ return FORMAT_ALIASES[key] ?? format;
744
+ }
745
+ function headersToRecord(headers) {
746
+ const out = {};
747
+ headers.forEach((value, key) => {
748
+ out[key] = value;
749
+ });
750
+ return out;
751
+ }
736
752
  var Legalize = class {
737
753
  countries;
738
754
  jurisdictions;
@@ -779,19 +795,100 @@ var Legalize = class {
779
795
  */
780
796
  async request(method, path, options = {}) {
781
797
  const upperMethod = method.toUpperCase();
782
- const url = this.buildUrl(path, options.params);
783
798
  const headers = { ...this._headers };
784
799
  if (options.extraHeaders) Object.assign(headers, options.extraHeaders);
785
800
  if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
786
801
  const hasJson = options.json !== void 0;
787
802
  if (hasJson) headers["Content-Type"] = "application/json";
803
+ const response = await this.sendWithRetry(upperMethod, path, headers, options, hasJson);
804
+ this._lastResponse = response;
805
+ if (response.status === 204) return null;
806
+ const text = await response.text();
807
+ if (!text) return null;
808
+ try {
809
+ return JSON.parse(text);
810
+ } catch (err) {
811
+ throw new APIError({
812
+ message: "Server returned non-JSON body",
813
+ statusCode: response.status,
814
+ body: text,
815
+ response,
816
+ cause: err
817
+ });
818
+ }
819
+ }
820
+ /**
821
+ * Execute a request and return the raw, non-JSON-decoded body.
822
+ *
823
+ * The escape hatch for content negotiation: the typed resource methods
824
+ * always return JSON models, but `requestRaw` lets you fetch any
825
+ * endpoint in another wire format. `format` controls the `Accept`
826
+ * header — `"xml"` (the default) requests `application/xml`, `"json"`
827
+ * requests `application/json`, and any other value is sent verbatim as
828
+ * the media type.
829
+ *
830
+ * The SDK has zero runtime deps and ships no XML parser — parse the
831
+ * returned `.text` (or `.content`) with your own library.
832
+ *
833
+ * Example:
834
+ *
835
+ * const res = await client.requestRaw("GET", "/api/v1/es/laws/BOE-A-1978-31229");
836
+ * res.contentType; // "application/xml; charset=utf-8"
837
+ * const xmlText = res.text; // already application/xml
838
+ *
839
+ * Throws the same APIError subclasses as {@link request} on a non-2xx
840
+ * response; the error body is in whatever format you negotiated.
841
+ */
842
+ async requestRaw(method, path, options = {}) {
843
+ const upperMethod = method.toUpperCase();
844
+ const headers = { ...this._headers };
845
+ headers["Accept"] = formatToAccept(options.format);
846
+ if (options.extraHeaders) Object.assign(headers, options.extraHeaders);
847
+ if (options.idempotencyKey) headers["Idempotency-Key"] = options.idempotencyKey;
848
+ const response = await this.sendWithRetry(upperMethod, path, headers, options, false);
849
+ this._lastResponse = response;
850
+ return rawFromResponse(response);
851
+ }
852
+ /** Release any resources held by the client. Kept for API symmetry. */
853
+ async close() {
854
+ }
855
+ /** TS 5.2+ `using` / `await using` support. */
856
+ async [Symbol.asyncDispose]() {
857
+ await this.close();
858
+ }
859
+ // ---- internals --------------------------------------------------------
860
+ buildUrl(path, params) {
861
+ let base;
862
+ if (path.startsWith("http://") || path.startsWith("https://")) {
863
+ base = path;
864
+ } else {
865
+ const p = path.startsWith("/") ? path : `/${path}`;
866
+ base = this._baseUrl + p;
867
+ }
868
+ if (!params) return base;
869
+ const query = buildQueryString(params);
870
+ if (!query) return base;
871
+ return base.includes("?") ? `${base}&${query}` : `${base}?${query}`;
872
+ }
873
+ /**
874
+ * Run the retry loop and return the raw 2xx `Response`.
875
+ *
876
+ * Shared by {@link request} (which then JSON-parses) and
877
+ * {@link requestRaw} (which builds a `RawResponse`). Applies the retry
878
+ * policy on transport errors and retryable statuses, populates
879
+ * `lastResponse` on the failing response before raising, and throws the
880
+ * typed `APIError` hierarchy on a final non-2xx. The returned response
881
+ * body is left unread for the caller to consume.
882
+ */
883
+ async sendWithRetry(method, path, headers, options, hasJson) {
884
+ const url = this.buildUrl(path, options.params);
788
885
  let attempt = 0;
789
886
  while (true) {
790
887
  let response;
791
888
  try {
792
- response = await this.sendOnce(url, upperMethod, headers, options, hasJson);
889
+ response = await this.sendOnce(url, method, headers, options, hasJson);
793
890
  } catch (err) {
794
- const shouldRetry2 = this._retry.shouldRetry(attempt, { method: upperMethod });
891
+ const shouldRetry2 = this._retry.shouldRetry(attempt, { method });
795
892
  if (!shouldRetry2) {
796
893
  throw wrapTransportError(err);
797
894
  }
@@ -801,25 +898,11 @@ var Legalize = class {
801
898
  continue;
802
899
  }
803
900
  if (response.status >= 200 && response.status < 300) {
804
- this._lastResponse = response;
805
- if (response.status === 204) return null;
806
- const text = await response.text();
807
- if (!text) return null;
808
- try {
809
- return JSON.parse(text);
810
- } catch (err) {
811
- throw new APIError({
812
- message: "Server returned non-JSON body",
813
- statusCode: response.status,
814
- body: text,
815
- response,
816
- cause: err
817
- });
818
- }
901
+ return response;
819
902
  }
820
903
  const shouldRetry = this._retry.shouldRetry(attempt, {
821
904
  status: response.status,
822
- method: upperMethod
905
+ method
823
906
  });
824
907
  if (!shouldRetry) {
825
908
  this._lastResponse = response;
@@ -835,27 +918,6 @@ var Legalize = class {
835
918
  attempt += 1;
836
919
  }
837
920
  }
838
- /** Release any resources held by the client. Kept for API symmetry. */
839
- async close() {
840
- }
841
- /** TS 5.2+ `using` / `await using` support. */
842
- async [Symbol.asyncDispose]() {
843
- await this.close();
844
- }
845
- // ---- internals --------------------------------------------------------
846
- buildUrl(path, params) {
847
- let base;
848
- if (path.startsWith("http://") || path.startsWith("https://")) {
849
- base = path;
850
- } else {
851
- const p = path.startsWith("/") ? path : `/${path}`;
852
- base = this._baseUrl + p;
853
- }
854
- if (!params) return base;
855
- const query = buildQueryString(params);
856
- if (!query) return base;
857
- return base.includes("?") ? `${base}&${query}` : `${base}?${query}`;
858
- }
859
921
  async sendOnce(url, method, headers, options, hasJson) {
860
922
  const controller = new AbortController();
861
923
  const timeoutMs = this._timeout;
@@ -933,6 +995,21 @@ async function errorFromResponse(response) {
933
995
  }
934
996
  return APIError.fromResponse(response, text, data);
935
997
  }
998
+ async function rawFromResponse(response) {
999
+ const buffer = await response.arrayBuffer();
1000
+ const content = new Uint8Array(buffer);
1001
+ const text = new TextDecoder().decode(content);
1002
+ return {
1003
+ statusCode: response.status,
1004
+ content,
1005
+ text,
1006
+ contentType: response.headers.get("content-type") ?? "",
1007
+ headers: headersToRecord(response.headers),
1008
+ json() {
1009
+ return JSON.parse(text);
1010
+ }
1011
+ };
1012
+ }
936
1013
  function wrapTransportError(err) {
937
1014
  if (err instanceof Error) {
938
1015
  const isAbort = err.name === "AbortError" || err.code === "ABORT_ERR";
@@ -1058,6 +1135,6 @@ function webhookEventFromPayload(payload) {
1058
1135
  };
1059
1136
  }
1060
1137
 
1061
- export { APIConnectionError, APIError, APITimeoutError, AuthenticationError, Countries, DEFAULT_API_VERSION, DEFAULT_BACKOFF_FACTOR, DEFAULT_BASE_URL, DEFAULT_INITIAL_DELAY, DEFAULT_MAX_DELAY, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_TOLERANCE_SECONDS, ForbiddenError, IDEMPOTENT_METHODS, InvalidRequestError, Jurisdictions, KEY_PREFIX, LawTypes, Laws, Legalize, LegalizeError, NotFoundError, OffsetIterator, PAGE_MAX, PageIterator, RETRY_STATUSES, RateLimitError, Reforms, RetryPolicy, SDK_VERSION, ServerError, ServiceUnavailableError, Stats, ValidationError, Webhook, WebhookVerificationError, Webhooks, buildQueryString, defaultUserAgent, parseRetryAfter, resolveApiKey, resolveApiVersion, resolveBaseUrl, sleep };
1138
+ export { APIConnectionError, APIError, APITimeoutError, AuthenticationError, Countries, DEFAULT_API_VERSION, DEFAULT_BACKOFF_FACTOR, DEFAULT_BASE_URL, DEFAULT_INITIAL_DELAY, DEFAULT_MAX_DELAY, DEFAULT_MAX_RETRIES, DEFAULT_TIMEOUT, DEFAULT_TOLERANCE_SECONDS, ForbiddenError, IDEMPOTENT_METHODS, InvalidRequestError, Jurisdictions, KEY_PREFIX, LawTypes, Laws, Legalize, LegalizeError, NotFoundError, OffsetIterator, PAGE_MAX, PageIterator, RETRY_STATUSES, RateLimitError, Reforms, RetryPolicy, SDK_VERSION, ServerError, ServiceUnavailableError, Stats, ValidationError, Webhook, WebhookVerificationError, Webhooks, buildQueryString, defaultUserAgent, formatToAccept, parseRetryAfter, resolveApiKey, resolveApiVersion, resolveBaseUrl, sleep };
1062
1139
  //# sourceMappingURL=index.js.map
1063
1140
  //# sourceMappingURL=index.js.map