@robosystems/client 0.3.39 → 0.3.41

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.
@@ -1,6 +1,6 @@
1
1
  import type { AssociationResponse, AutoMapElementsOperation, CreateAgentRequest, CreateEventBlockRequest, CreateEventHandlerRequest, CreateInformationBlockRequest, CreateMappingAssociationOperation, CreateTaxonomyBlockRequest, CreateViewRequest, DeleteInformationBlockRequest, DeleteInformationBlockResponse, DeleteMappingAssociationOperation, DeleteResult, DeleteTaxonomyBlockRequest, DeleteTaxonomyBlockResponse, EntityTaxonomyResponse, EvaluateRulesRequest, EvaluateRulesResponse, EventBlockEnvelope, EventHandlerResponse, FinancialStatementAnalysisRequest, InformationBlockEnvelope, JournalEntryResponse, LedgerAgentResponse, LinkEntityTaxonomyRequest, LiveFinancialStatementRequest, OperationEnvelope, PreviewEventBlockResponse, PublishListMemberResponse, PublishListResponse, ReportResponse, ShareReportResponse, TaxonomyBlockEnvelope, UpdateAgentRequest, UpdateEntityRequest, UpdateEventBlockRequest, UpdateEventHandlerRequest, UpdateInformationBlockRequest, UpdateJournalEntryRequest, UpdateTaxonomyBlockRequest } from '../types.gen';
2
2
  import type { TokenProvider } from './graphql/client';
3
- import { type GetInformationBlockQuery, type GetLedgerAccountRollupsQuery, type GetLedgerAccountTreeQuery, type GetLedgerAgentQuery, type GetLedgerClosingBookStructuresQuery, type GetLedgerEntityQuery, type GetLedgerEventBlockQuery, type GetLedgerFiscalCalendarQuery, type GetLedgerMappedTrialBalanceQuery, type GetLedgerMappingCoverageQuery, type GetLedgerMappingQuery, type GetLedgerPeriodCloseStatusQuery, type GetLedgerPeriodDraftsQuery, type GetLedgerPublishListQuery, type GetLedgerReportingTaxonomyQuery, type GetLedgerReportPackageQuery, type GetLedgerReportQuery, type GetLedgerStatementQuery, type GetLedgerSummaryQuery, type GetLedgerTransactionQuery, type GetLedgerTrialBalanceQuery, type ListInformationBlocksQuery, type ListLedgerAccountsQuery, type ListLedgerAgentsQuery, type ListLedgerElementsQuery, type ListLedgerEntitiesQuery, type ListLedgerEventBlocksQuery, type ListLedgerMappingsQuery, type ListLedgerPublishListsQuery, type ListLedgerReportsQuery, type ListLedgerStructuresQuery, type ListLedgerTaxonomiesQuery, type ListLedgerTransactionsQuery, type ListLedgerUnmappedElementsQuery, type MappingCandidatesQuery } from './graphql/generated/graphql';
3
+ import { type GetInformationBlockQuery, type GetLedgerAccountRollupsQuery, type GetLedgerAccountTreeQuery, type GetLedgerAgentQuery, type GetLedgerClosingBookStructuresQuery, type GetLedgerEntityQuery, type GetLedgerEventBlockQuery, type GetLedgerFiscalCalendarQuery, type GetLedgerMappedTrialBalanceQuery, type GetLedgerMappingCoverageQuery, type GetLedgerMappingQuery, type GetLedgerPeriodCloseStatusQuery, type GetLedgerPeriodDraftsQuery, type GetLedgerPublishListQuery, type GetLedgerReportingTaxonomyQuery, type GetLedgerReportPackageQuery, type GetLedgerReportQuery, type GetLedgerStatementQuery, type GetLedgerSummaryQuery, type GetLedgerTransactionQuery, type GetLedgerTrialBalanceQuery, type ListInformationBlocksQuery, type ListLedgerAccountsQuery, type ListLedgerAgentsQuery, type ListLedgerElementsQuery, type ListLedgerEntitiesQuery, type ListLedgerEventBlocksQuery, type ListLedgerMappingsQuery, type ListLedgerPublishListsQuery, type ListLedgerReportsQuery, type ListLedgerStructuresQuery, type ListLedgerTaxonomiesQuery, type ListLedgerTransactionsQuery, type ListLedgerUnmappedElementsQuery, type MappingCandidatesQuery, type ReportDownloadFormat } from './graphql/generated/graphql';
4
4
  export type LedgerEntity = NonNullable<GetLedgerEntityQuery['entity']>;
5
5
  export type LedgerEntitySummary = ListLedgerEntitiesQuery['entities'][number];
6
6
  export type LedgerSummary = NonNullable<GetLedgerSummaryQuery['summary']>;
@@ -56,7 +56,7 @@ export type PublishListMember = PublishListDetail['members'][number];
56
56
  /**
57
57
  * Presigned-URL response for a Report bundle download.
58
58
  *
59
- * Returned by ``LedgerClient.getReportBundleDownloadUrl`` — the
59
+ * Returned by ``LedgerClient.getReportDownloadUrl`` — the
60
60
  * ``downloadUrl`` is a time-limited URL that streams the
61
61
  * serialization artifact directly from object storage. Browser
62
62
  * callers typically follow the URL via ``window.location.href`` or
@@ -69,7 +69,7 @@ export interface ReportBundleDownloadResponse {
69
69
  expiresAt: string;
70
70
  /** MIME type of the artifact behind the URL. */
71
71
  contentType: string;
72
- /** Serialization flavor — ``jsonld`` is the Phase 1a default. */
72
+ /** Serialization flavor — ``jsonld`` or ``xbrl-2.1``. */
73
73
  format: string;
74
74
  /** Bundle generation number stamped on the Report. */
75
75
  generationCount: number;
@@ -406,6 +406,22 @@ export declare class LedgerClient {
406
406
  }): Promise<InformationBlockEnvelope>;
407
407
  /** Permanently delete a schedule (cascades through facts + associations). */
408
408
  deleteSchedule(graphId: string, structureId: string): Promise<DeleteInformationBlockResponse>;
409
+ /**
410
+ * Rebuild a schedule in place — atomic alternative to
411
+ * delete-then-recreate (which orphans pending obligations). Preserves
412
+ * the structure id, element associations, and taxonomy; voids the old
413
+ * pending obligation chain; deletes the old facts + SumEquals rules;
414
+ * and regenerates fresh forward facts + a fresh obligation chain from
415
+ * the schedule's stored definition. The historical-vs-in-scope split
416
+ * is re-derived from the CURRENT fiscal calendar `closed_through`, so
417
+ * a rebuild re-scopes the schedule to today's close state. Use this to
418
+ * pick up a fixed generator without orphaning obligations.
419
+ *
420
+ * Returns the same shape as `createSchedule`.
421
+ */
422
+ rebuildSchedule(graphId: string, structureId: string, options?: {
423
+ idempotencyKey?: string;
424
+ }): Promise<ScheduleCreated>;
409
425
  /**
410
426
  * Dispose of a schedule asset — atomically truncates forward facts,
411
427
  * deletes the SumEquals rule, and posts a balanced disposal entry.
@@ -587,51 +603,29 @@ export declare class LedgerClient {
587
603
  * Get a short-lived presigned URL for downloading a published
588
604
  * Report's serialization bundle.
589
605
  *
590
- * Phase 1a only resolves the JSON-LD flavor; reserved RDF and XBRL
591
- * flavors return HTTP 400 until their producers ship. Reports
592
- * published before the serialization feature shipped will resolve
593
- * to a 404 those Reports have no stamped bundle_url and must be
594
- * regenerated to produce one.
606
+ * A download is a read, so this resolves through GraphQL
607
+ * (`reportDownloadUrl`) the retired `GET .../reports/{id}/download`
608
+ * REST resource is gone. Every flavor resolves to a presigned S3 URL:
609
+ * JSON-LD is stamped at publish time; XBRL is materialized + cached
610
+ * on first request. The returned `downloadUrl` is valid for
611
+ * `expiresIn` seconds (default 300, max 3600); browser callers
612
+ * navigate to it via `window.location.href` (or an `<a href>` click)
613
+ * to trigger the download — the server-set Content-Disposition forces
614
+ * "attachment" with a versioned filename.
595
615
  *
596
- * The returned ``downloadUrl`` is a presigned S3 URL valid for
597
- * ``expiresIn`` seconds (default 300, max 3600). Browser callers
598
- * typically navigate to it via ``window.location.href`` (or assign
599
- * to an `<a href>` and click) to trigger the file download; the
600
- * server-set Content-Disposition forces "attachment" with a
601
- * versioned filename.
616
+ * Returns `null` when the report doesn't exist. A report that has
617
+ * never been published surfaces a `REPORT_BUNDLE_NOT_AVAILABLE`
618
+ * GraphQL error (regenerate it to produce a bundle).
602
619
  *
603
620
  * @param graphId Graph identifier owning the Report.
604
621
  * @param reportId Report identifier (rpt_-prefixed ULID).
605
- * @param options.format Serialization flavor — defaults to ``'jsonld'``.
622
+ * @param options.format Serialization flavor — `JSONLD` (default) or `XBRL_2_1`.
606
623
  * @param options.expiresIn Presigned URL lifetime, in seconds.
607
624
  */
608
- getReportBundleDownloadUrl(graphId: string, reportId: string, options?: {
609
- format?: string;
625
+ getReportDownloadUrl(graphId: string, reportId: string, options?: {
626
+ format?: ReportDownloadFormat;
610
627
  expiresIn?: number;
611
- }): Promise<ReportBundleDownloadResponse>;
612
- /**
613
- * Download the Report's serialization bundle as an XBRL 2.1 zip.
614
- *
615
- * Rebuilds the bundle on the server and streams the zip body
616
- * directly — no S3 presigned URL is involved (XBRL is on-demand
617
- * emit, per the serialization spec). The returned ``Blob`` can be
618
- * saved via URL.createObjectURL + a temporary anchor click to
619
- * trigger the browser download dialog.
620
- *
621
- * The zip contains five files: ``instance.xml``, ``report.xsd``,
622
- * ``report-pre.xml``, ``report-cal.xml``, ``report-def.xml``.
623
- *
624
- * @param graphId Graph identifier owning the Report.
625
- * @param reportId Report identifier (rpt_-prefixed ULID).
626
- * @param options.flavor XBRL flavor (default ``'xbrl-2.1'``).
627
- */
628
- getReportBundleXbrlZip(graphId: string, reportId: string, options?: {
629
- flavor?: string;
630
- }): Promise<{
631
- blob: Blob;
632
- filename: string;
633
- generationCount: number | null;
634
- }>;
628
+ }): Promise<ReportBundleDownloadResponse | null>;
635
629
  /**
636
630
  * Share a published report to every member of a publish list. Each
637
631
  * target graph receives a snapshot copy of the report's facts.
@@ -357,6 +357,38 @@ class LedgerClient {
357
357
  const envelope = await this.callOperation('Delete schedule', (0, sdk_gen_1.opDeleteInformationBlock)({ path: { graph_id: graphId }, body }));
358
358
  return (envelope.result ?? { deleted: true });
359
359
  }
360
+ /**
361
+ * Rebuild a schedule in place — atomic alternative to
362
+ * delete-then-recreate (which orphans pending obligations). Preserves
363
+ * the structure id, element associations, and taxonomy; voids the old
364
+ * pending obligation chain; deletes the old facts + SumEquals rules;
365
+ * and regenerates fresh forward facts + a fresh obligation chain from
366
+ * the schedule's stored definition. The historical-vs-in-scope split
367
+ * is re-derived from the CURRENT fiscal calendar `closed_through`, so
368
+ * a rebuild re-scopes the schedule to today's close state. Use this to
369
+ * pick up a fixed generator without orphaning obligations.
370
+ *
371
+ * Returns the same shape as `createSchedule`.
372
+ */
373
+ async rebuildSchedule(graphId, structureId, options) {
374
+ const body = { structure_id: structureId };
375
+ const envelope = await this.callOperation('Rebuild schedule', (0, sdk_gen_1.opRebuildSchedule)({
376
+ path: { graph_id: graphId },
377
+ body,
378
+ headers: options?.idempotencyKey
379
+ ? { 'Idempotency-Key': options.idempotencyKey }
380
+ : undefined,
381
+ }));
382
+ const raw = envelope.result;
383
+ return {
384
+ structureId: raw.structure_id,
385
+ name: raw.name,
386
+ taxonomyId: raw.taxonomy_id,
387
+ totalPeriods: raw.total_periods,
388
+ totalFacts: raw.total_facts,
389
+ ruleSummary: raw.rule_summary ?? null,
390
+ };
391
+ }
360
392
  /**
361
393
  * Dispose of a schedule asset — atomically truncates forward facts,
362
394
  * deletes the SumEquals rule, and posts a balanced disposal entry.
@@ -768,115 +800,42 @@ class LedgerClient {
768
800
  * Get a short-lived presigned URL for downloading a published
769
801
  * Report's serialization bundle.
770
802
  *
771
- * Phase 1a only resolves the JSON-LD flavor; reserved RDF and XBRL
772
- * flavors return HTTP 400 until their producers ship. Reports
773
- * published before the serialization feature shipped will resolve
774
- * to a 404 those Reports have no stamped bundle_url and must be
775
- * regenerated to produce one.
803
+ * A download is a read, so this resolves through GraphQL
804
+ * (`reportDownloadUrl`) the retired `GET .../reports/{id}/download`
805
+ * REST resource is gone. Every flavor resolves to a presigned S3 URL:
806
+ * JSON-LD is stamped at publish time; XBRL is materialized + cached
807
+ * on first request. The returned `downloadUrl` is valid for
808
+ * `expiresIn` seconds (default 300, max 3600); browser callers
809
+ * navigate to it via `window.location.href` (or an `<a href>` click)
810
+ * to trigger the download — the server-set Content-Disposition forces
811
+ * "attachment" with a versioned filename.
776
812
  *
777
- * The returned ``downloadUrl`` is a presigned S3 URL valid for
778
- * ``expiresIn`` seconds (default 300, max 3600). Browser callers
779
- * typically navigate to it via ``window.location.href`` (or assign
780
- * to an `<a href>` and click) to trigger the file download; the
781
- * server-set Content-Disposition forces "attachment" with a
782
- * versioned filename.
813
+ * Returns `null` when the report doesn't exist. A report that has
814
+ * never been published surfaces a `REPORT_BUNDLE_NOT_AVAILABLE`
815
+ * GraphQL error (regenerate it to produce a bundle).
783
816
  *
784
817
  * @param graphId Graph identifier owning the Report.
785
818
  * @param reportId Report identifier (rpt_-prefixed ULID).
786
- * @param options.format Serialization flavor — defaults to ``'jsonld'``.
819
+ * @param options.format Serialization flavor — `JSONLD` (default) or `XBRL_2_1`.
787
820
  * @param options.expiresIn Presigned URL lifetime, in seconds.
788
821
  */
789
- async getReportBundleDownloadUrl(graphId, reportId, options = {}) {
790
- const format = options.format ?? 'jsonld';
791
- const expiresIn = options.expiresIn ?? 300;
792
- const url = `${this.config.baseUrl.replace(/\/$/, '')}` +
793
- `/extensions/roboledger/${encodeURIComponent(graphId)}` +
794
- `/reports/${encodeURIComponent(reportId)}/download` +
795
- `?format=${encodeURIComponent(format)}&expires_in=${expiresIn}`;
796
- const headers = new Headers({ Accept: 'application/json', ...(this.config.headers ?? {}) });
797
- const token = await this.resolveToken();
798
- if (token) {
799
- // ``rfs…`` long-lived keys go in X-API-Key; everything else is
800
- // a short-lived JWT. Mirrors the GraphQL client behaviour at
801
- // ``clients/graphql/client.ts``.
802
- if (token.startsWith('rfs')) {
803
- headers.set('X-API-Key', token);
804
- }
805
- else {
806
- headers.set('Authorization', `Bearer ${token}`);
807
- }
808
- }
809
- const response = await fetch(url, {
810
- method: 'GET',
811
- headers,
812
- credentials: this.config.credentials,
813
- });
814
- if (!response.ok) {
815
- const body = await response.text();
816
- throw new Error(`Get report bundle download URL failed (${response.status}): ${body}`);
817
- }
818
- const raw = (await response.json());
819
- return {
820
- downloadUrl: raw.download_url,
821
- expiresAt: raw.expires_at,
822
- contentType: raw.content_type,
823
- format: raw.format,
824
- generationCount: raw.generation_count,
825
- };
826
- }
827
- /**
828
- * Download the Report's serialization bundle as an XBRL 2.1 zip.
829
- *
830
- * Rebuilds the bundle on the server and streams the zip body
831
- * directly — no S3 presigned URL is involved (XBRL is on-demand
832
- * emit, per the serialization spec). The returned ``Blob`` can be
833
- * saved via URL.createObjectURL + a temporary anchor click to
834
- * trigger the browser download dialog.
835
- *
836
- * The zip contains five files: ``instance.xml``, ``report.xsd``,
837
- * ``report-pre.xml``, ``report-cal.xml``, ``report-def.xml``.
838
- *
839
- * @param graphId Graph identifier owning the Report.
840
- * @param reportId Report identifier (rpt_-prefixed ULID).
841
- * @param options.flavor XBRL flavor (default ``'xbrl-2.1'``).
842
- */
843
- async getReportBundleXbrlZip(graphId, reportId, options = {}) {
844
- const flavor = options.flavor ?? 'xbrl-2.1';
845
- const url = `${this.config.baseUrl.replace(/\/$/, '')}` +
846
- `/extensions/roboledger/${encodeURIComponent(graphId)}` +
847
- `/reports/${encodeURIComponent(reportId)}/download` +
848
- `?format=${encodeURIComponent(flavor)}`;
849
- const headers = new Headers({
850
- Accept: 'application/zip',
851
- ...(this.config.headers ?? {}),
852
- });
853
- const token = await this.resolveToken();
854
- if (token) {
855
- if (token.startsWith('rfs')) {
856
- headers.set('X-API-Key', token);
857
- }
858
- else {
859
- headers.set('Authorization', `Bearer ${token}`);
860
- }
861
- }
862
- const response = await fetch(url, {
863
- method: 'GET',
864
- headers,
865
- credentials: this.config.credentials,
822
+ async getReportDownloadUrl(graphId, reportId, options = {}) {
823
+ return this.gqlQuery(graphId, graphql_1.GetLedgerReportDownloadUrlDocument, {
824
+ reportId,
825
+ format: options.format ?? 'JSONLD',
826
+ expiresIn: options.expiresIn ?? 300,
827
+ }, 'Get report download URL', (data) => {
828
+ const node = data.reportDownloadUrl;
829
+ if (!node)
830
+ return null;
831
+ return {
832
+ downloadUrl: node.downloadUrl,
833
+ expiresAt: node.expiresAt,
834
+ contentType: node.contentType,
835
+ format: node.format,
836
+ generationCount: node.generationCount,
837
+ };
866
838
  });
867
- if (!response.ok) {
868
- const body = await response.text();
869
- throw new Error(`Get report bundle XBRL zip failed (${response.status}): ${body}`);
870
- }
871
- const blob = await response.blob();
872
- // Parse Content-Disposition for the server-suggested filename;
873
- // fall back to a sensible default if absent.
874
- const disposition = response.headers.get('Content-Disposition') ?? '';
875
- const filenameMatch = disposition.match(/filename="?([^";]+)"?/i);
876
- const filename = filenameMatch ? filenameMatch[1] : `${reportId}.zip`;
877
- const generationHeader = response.headers.get('X-Bundle-Generation');
878
- const generationCount = generationHeader ? Number.parseInt(generationHeader, 10) : null;
879
- return { blob, filename, generationCount };
880
839
  }
881
840
  /**
882
841
  * Share a published report to every member of a publish list. Each
@@ -50,6 +50,7 @@ import {
50
50
  opLinkEntityTaxonomy,
51
51
  opLiveFinancialStatement,
52
52
  opPreviewEventBlock,
53
+ opRebuildSchedule,
53
54
  opRegenerateReport,
54
55
  opRemovePublishListMember,
55
56
  opReopenPeriod,
@@ -103,6 +104,7 @@ import type {
103
104
  PreviewEventBlockResponse,
104
105
  PublishListMemberResponse,
105
106
  PublishListResponse,
107
+ RebuildScheduleRequest,
106
108
  ReopenPeriodOperation,
107
109
  ReportResponse,
108
110
  SetCloseTargetOperation,
@@ -136,6 +138,7 @@ import {
136
138
  GetLedgerPeriodDraftsDocument,
137
139
  GetLedgerPublishListDocument,
138
140
  GetLedgerReportDocument,
141
+ GetLedgerReportDownloadUrlDocument,
139
142
  GetLedgerReportingTaxonomyDocument,
140
143
  GetLedgerReportPackageDocument,
141
144
  GetLedgerStatementDocument,
@@ -191,6 +194,7 @@ import {
191
194
  type ListLedgerTransactionsQuery,
192
195
  type ListLedgerUnmappedElementsQuery,
193
196
  type MappingCandidatesQuery,
197
+ type ReportDownloadFormat,
194
198
  } from './graphql/generated/graphql'
195
199
 
196
200
  // ── Friendly types derived from GraphQL codegen ────────────────────────
@@ -283,7 +287,7 @@ export type PublishListMember = PublishListDetail['members'][number]
283
287
  /**
284
288
  * Presigned-URL response for a Report bundle download.
285
289
  *
286
- * Returned by ``LedgerClient.getReportBundleDownloadUrl`` — the
290
+ * Returned by ``LedgerClient.getReportDownloadUrl`` — the
287
291
  * ``downloadUrl`` is a time-limited URL that streams the
288
292
  * serialization artifact directly from object storage. Browser
289
293
  * callers typically follow the URL via ``window.location.href`` or
@@ -296,20 +300,12 @@ export interface ReportBundleDownloadResponse {
296
300
  expiresAt: string
297
301
  /** MIME type of the artifact behind the URL. */
298
302
  contentType: string
299
- /** Serialization flavor — ``jsonld`` is the Phase 1a default. */
303
+ /** Serialization flavor — ``jsonld`` or ``xbrl-2.1``. */
300
304
  format: string
301
305
  /** Bundle generation number stamped on the Report. */
302
306
  generationCount: number
303
307
  }
304
308
 
305
- interface RawReportBundleDownloadResponse {
306
- download_url: string
307
- expires_at: string
308
- content_type: string
309
- format: string
310
- generation_count: number
311
- }
312
-
313
309
  export interface PeriodSpecInput {
314
310
  start: string
315
311
  end: string
@@ -1237,6 +1233,46 @@ export class LedgerClient {
1237
1233
  return (envelope.result ?? { deleted: true }) as DeleteInformationBlockResponse
1238
1234
  }
1239
1235
 
1236
+ /**
1237
+ * Rebuild a schedule in place — atomic alternative to
1238
+ * delete-then-recreate (which orphans pending obligations). Preserves
1239
+ * the structure id, element associations, and taxonomy; voids the old
1240
+ * pending obligation chain; deletes the old facts + SumEquals rules;
1241
+ * and regenerates fresh forward facts + a fresh obligation chain from
1242
+ * the schedule's stored definition. The historical-vs-in-scope split
1243
+ * is re-derived from the CURRENT fiscal calendar `closed_through`, so
1244
+ * a rebuild re-scopes the schedule to today's close state. Use this to
1245
+ * pick up a fixed generator without orphaning obligations.
1246
+ *
1247
+ * Returns the same shape as `createSchedule`.
1248
+ */
1249
+ async rebuildSchedule(
1250
+ graphId: string,
1251
+ structureId: string,
1252
+ options?: { idempotencyKey?: string }
1253
+ ): Promise<ScheduleCreated> {
1254
+ const body: RebuildScheduleRequest = { structure_id: structureId }
1255
+ const envelope = await this.callOperation(
1256
+ 'Rebuild schedule',
1257
+ opRebuildSchedule({
1258
+ path: { graph_id: graphId },
1259
+ body,
1260
+ headers: options?.idempotencyKey
1261
+ ? { 'Idempotency-Key': options.idempotencyKey }
1262
+ : undefined,
1263
+ })
1264
+ )
1265
+ const raw = envelope.result as unknown as RawScheduleCreatedResult
1266
+ return {
1267
+ structureId: raw.structure_id,
1268
+ name: raw.name,
1269
+ taxonomyId: raw.taxonomy_id,
1270
+ totalPeriods: raw.total_periods,
1271
+ totalFacts: raw.total_facts,
1272
+ ruleSummary: raw.rule_summary ?? null,
1273
+ }
1274
+ }
1275
+
1240
1276
  /**
1241
1277
  * Dispose of a schedule asset — atomically truncates forward facts,
1242
1278
  * deletes the SumEquals rule, and posts a balanced disposal entry.
@@ -1903,124 +1939,51 @@ export class LedgerClient {
1903
1939
  * Get a short-lived presigned URL for downloading a published
1904
1940
  * Report's serialization bundle.
1905
1941
  *
1906
- * Phase 1a only resolves the JSON-LD flavor; reserved RDF and XBRL
1907
- * flavors return HTTP 400 until their producers ship. Reports
1908
- * published before the serialization feature shipped will resolve
1909
- * to a 404 those Reports have no stamped bundle_url and must be
1910
- * regenerated to produce one.
1942
+ * A download is a read, so this resolves through GraphQL
1943
+ * (`reportDownloadUrl`) the retired `GET .../reports/{id}/download`
1944
+ * REST resource is gone. Every flavor resolves to a presigned S3 URL:
1945
+ * JSON-LD is stamped at publish time; XBRL is materialized + cached
1946
+ * on first request. The returned `downloadUrl` is valid for
1947
+ * `expiresIn` seconds (default 300, max 3600); browser callers
1948
+ * navigate to it via `window.location.href` (or an `<a href>` click)
1949
+ * to trigger the download — the server-set Content-Disposition forces
1950
+ * "attachment" with a versioned filename.
1911
1951
  *
1912
- * The returned ``downloadUrl`` is a presigned S3 URL valid for
1913
- * ``expiresIn`` seconds (default 300, max 3600). Browser callers
1914
- * typically navigate to it via ``window.location.href`` (or assign
1915
- * to an `<a href>` and click) to trigger the file download; the
1916
- * server-set Content-Disposition forces "attachment" with a
1917
- * versioned filename.
1952
+ * Returns `null` when the report doesn't exist. A report that has
1953
+ * never been published surfaces a `REPORT_BUNDLE_NOT_AVAILABLE`
1954
+ * GraphQL error (regenerate it to produce a bundle).
1918
1955
  *
1919
1956
  * @param graphId Graph identifier owning the Report.
1920
1957
  * @param reportId Report identifier (rpt_-prefixed ULID).
1921
- * @param options.format Serialization flavor — defaults to ``'jsonld'``.
1958
+ * @param options.format Serialization flavor — `JSONLD` (default) or `XBRL_2_1`.
1922
1959
  * @param options.expiresIn Presigned URL lifetime, in seconds.
1923
1960
  */
1924
- async getReportBundleDownloadUrl(
1961
+ async getReportDownloadUrl(
1925
1962
  graphId: string,
1926
1963
  reportId: string,
1927
- options: { format?: string; expiresIn?: number } = {}
1928
- ): Promise<ReportBundleDownloadResponse> {
1929
- const format = options.format ?? 'jsonld'
1930
- const expiresIn = options.expiresIn ?? 300
1931
- const url =
1932
- `${this.config.baseUrl.replace(/\/$/, '')}` +
1933
- `/extensions/roboledger/${encodeURIComponent(graphId)}` +
1934
- `/reports/${encodeURIComponent(reportId)}/download` +
1935
- `?format=${encodeURIComponent(format)}&expires_in=${expiresIn}`
1936
- const headers = new Headers({ Accept: 'application/json', ...(this.config.headers ?? {}) })
1937
- const token = await this.resolveToken()
1938
- if (token) {
1939
- // ``rfs…`` long-lived keys go in X-API-Key; everything else is
1940
- // a short-lived JWT. Mirrors the GraphQL client behaviour at
1941
- // ``clients/graphql/client.ts``.
1942
- if (token.startsWith('rfs')) {
1943
- headers.set('X-API-Key', token)
1944
- } else {
1945
- headers.set('Authorization', `Bearer ${token}`)
1946
- }
1947
- }
1948
- const response = await fetch(url, {
1949
- method: 'GET',
1950
- headers,
1951
- credentials: this.config.credentials,
1952
- })
1953
- if (!response.ok) {
1954
- const body = await response.text()
1955
- throw new Error(`Get report bundle download URL failed (${response.status}): ${body}`)
1956
- }
1957
- const raw = (await response.json()) as RawReportBundleDownloadResponse
1958
- return {
1959
- downloadUrl: raw.download_url,
1960
- expiresAt: raw.expires_at,
1961
- contentType: raw.content_type,
1962
- format: raw.format,
1963
- generationCount: raw.generation_count,
1964
- }
1965
- }
1966
-
1967
- /**
1968
- * Download the Report's serialization bundle as an XBRL 2.1 zip.
1969
- *
1970
- * Rebuilds the bundle on the server and streams the zip body
1971
- * directly — no S3 presigned URL is involved (XBRL is on-demand
1972
- * emit, per the serialization spec). The returned ``Blob`` can be
1973
- * saved via URL.createObjectURL + a temporary anchor click to
1974
- * trigger the browser download dialog.
1975
- *
1976
- * The zip contains five files: ``instance.xml``, ``report.xsd``,
1977
- * ``report-pre.xml``, ``report-cal.xml``, ``report-def.xml``.
1978
- *
1979
- * @param graphId Graph identifier owning the Report.
1980
- * @param reportId Report identifier (rpt_-prefixed ULID).
1981
- * @param options.flavor XBRL flavor (default ``'xbrl-2.1'``).
1982
- */
1983
- async getReportBundleXbrlZip(
1984
- graphId: string,
1985
- reportId: string,
1986
- options: { flavor?: string } = {}
1987
- ): Promise<{ blob: Blob; filename: string; generationCount: number | null }> {
1988
- const flavor = options.flavor ?? 'xbrl-2.1'
1989
- const url =
1990
- `${this.config.baseUrl.replace(/\/$/, '')}` +
1991
- `/extensions/roboledger/${encodeURIComponent(graphId)}` +
1992
- `/reports/${encodeURIComponent(reportId)}/download` +
1993
- `?format=${encodeURIComponent(flavor)}`
1994
- const headers = new Headers({
1995
- Accept: 'application/zip',
1996
- ...(this.config.headers ?? {}),
1997
- })
1998
- const token = await this.resolveToken()
1999
- if (token) {
2000
- if (token.startsWith('rfs')) {
2001
- headers.set('X-API-Key', token)
2002
- } else {
2003
- headers.set('Authorization', `Bearer ${token}`)
1964
+ options: { format?: ReportDownloadFormat; expiresIn?: number } = {}
1965
+ ): Promise<ReportBundleDownloadResponse | null> {
1966
+ return this.gqlQuery(
1967
+ graphId,
1968
+ GetLedgerReportDownloadUrlDocument,
1969
+ {
1970
+ reportId,
1971
+ format: options.format ?? 'JSONLD',
1972
+ expiresIn: options.expiresIn ?? 300,
1973
+ },
1974
+ 'Get report download URL',
1975
+ (data) => {
1976
+ const node = data.reportDownloadUrl
1977
+ if (!node) return null
1978
+ return {
1979
+ downloadUrl: node.downloadUrl,
1980
+ expiresAt: node.expiresAt,
1981
+ contentType: node.contentType,
1982
+ format: node.format,
1983
+ generationCount: node.generationCount,
1984
+ }
2004
1985
  }
2005
- }
2006
- const response = await fetch(url, {
2007
- method: 'GET',
2008
- headers,
2009
- credentials: this.config.credentials,
2010
- })
2011
- if (!response.ok) {
2012
- const body = await response.text()
2013
- throw new Error(`Get report bundle XBRL zip failed (${response.status}): ${body}`)
2014
- }
2015
- const blob = await response.blob()
2016
- // Parse Content-Disposition for the server-suggested filename;
2017
- // fall back to a sensible default if absent.
2018
- const disposition = response.headers.get('Content-Disposition') ?? ''
2019
- const filenameMatch = disposition.match(/filename="?([^";]+)"?/i)
2020
- const filename = filenameMatch ? filenameMatch[1] : `${reportId}.zip`
2021
- const generationHeader = response.headers.get('X-Bundle-Generation')
2022
- const generationCount = generationHeader ? Number.parseInt(generationHeader, 10) : null
2023
- return { blob, filename, generationCount }
1986
+ )
2024
1987
  }
2025
1988
 
2026
1989
  /**