@transcend-io/cli 10.16.0 → 10.17.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/README.md +13 -2
- package/dist/{app-COX_n1FP.mjs → app-D_h2VQYJ.mjs} +18 -18
- package/dist/{app-COX_n1FP.mjs.map → app-D_h2VQYJ.mjs.map} +1 -1
- package/dist/{approvePrivacyRequests-DJItn7R6.mjs → approvePrivacyRequests-CQzypOiU.mjs} +2 -2
- package/dist/{approvePrivacyRequests-DJItn7R6.mjs.map → approvePrivacyRequests-CQzypOiU.mjs.map} +1 -1
- package/dist/bin/bash-complete.mjs +1 -1
- package/dist/bin/cli.mjs +1 -1
- package/dist/bin/deprecated-command.mjs +1 -1
- package/dist/{buildExamples-CZ8kcEqO.mjs → buildExamples-CbXqb5QS.mjs} +2 -2
- package/dist/{buildExamples-CZ8kcEqO.mjs.map → buildExamples-CbXqb5QS.mjs.map} +1 -1
- package/dist/{buildXdiSyncEndpoint-CSGwr2H5.mjs → buildXdiSyncEndpoint-16mdsBxp.mjs} +2 -2
- package/dist/{buildXdiSyncEndpoint-CSGwr2H5.mjs.map → buildXdiSyncEndpoint-16mdsBxp.mjs.map} +1 -1
- package/dist/{bulkRestartRequests-D-t58SeM.mjs → bulkRestartRequests-Bo_R-Bu6.mjs} +2 -2
- package/dist/{bulkRestartRequests-D-t58SeM.mjs.map → bulkRestartRequests-Bo_R-Bu6.mjs.map} +1 -1
- package/dist/{bulkRetryEnrichers-BmwRLF2w.mjs → bulkRetryEnrichers-By8Lzu96.mjs} +2 -2
- package/dist/{bulkRetryEnrichers-BmwRLF2w.mjs.map → bulkRetryEnrichers-By8Lzu96.mjs.map} +1 -1
- package/dist/{cancelPrivacyRequests-CFCULeSv.mjs → cancelPrivacyRequests-RygRMV5h.mjs} +2 -2
- package/dist/{cancelPrivacyRequests-CFCULeSv.mjs.map → cancelPrivacyRequests-RygRMV5h.mjs.map} +1 -1
- package/dist/codecs-CwRYEdQK.mjs +2 -0
- package/dist/{codecs-DW8SY77H.mjs.map → codecs-CwRYEdQK.mjs.map} +1 -1
- package/dist/{command-8AnltUOG.mjs → command-q8MOijXW.mjs} +2 -2
- package/dist/{command-8AnltUOG.mjs.map → command-q8MOijXW.mjs.map} +1 -1
- package/dist/constants-BeMH_MWn.mjs +2 -0
- package/dist/constants-BeMH_MWn.mjs.map +1 -0
- package/dist/{createExtraKeyHandler-DqiD609a.mjs → createExtraKeyHandler-BckC7HfJ.mjs} +2 -2
- package/dist/{createExtraKeyHandler-DqiD609a.mjs.map → createExtraKeyHandler-BckC7HfJ.mjs.map} +1 -1
- package/dist/{downloadPrivacyRequestFiles-B7HNc-Y1.mjs → downloadPrivacyRequestFiles-DWUBzMms.mjs} +2 -2
- package/dist/{downloadPrivacyRequestFiles-B7HNc-Y1.mjs.map → downloadPrivacyRequestFiles-DWUBzMms.mjs.map} +1 -1
- package/dist/{enums-CyFTrzXY.mjs → enums-mAXnon_I.mjs} +2 -2
- package/dist/enums-mAXnon_I.mjs.map +1 -0
- package/dist/{generateCrossAccountApiKeys-kqzneWII.mjs → generateCrossAccountApiKeys-Ch6311KG.mjs} +2 -2
- package/dist/{generateCrossAccountApiKeys-kqzneWII.mjs.map → generateCrossAccountApiKeys-Ch6311KG.mjs.map} +1 -1
- package/dist/{impl-DTDnN3Hn.mjs → impl-5oKENBqi.mjs} +2 -2
- package/dist/{impl-DTDnN3Hn.mjs.map → impl-5oKENBqi.mjs.map} +1 -1
- package/dist/{impl-Bkdu-3yo.mjs → impl-84Ex4gfr.mjs} +2 -2
- package/dist/{impl-Bkdu-3yo.mjs.map → impl-84Ex4gfr.mjs.map} +1 -1
- package/dist/{impl-BVcXWJyI2.mjs → impl-B1jA1R3_2.mjs} +2 -2
- package/dist/{impl-BVcXWJyI2.mjs.map → impl-B1jA1R3_2.mjs.map} +1 -1
- package/dist/{impl-RqwgNa2O.mjs → impl-B3bx4FKy.mjs} +2 -2
- package/dist/{impl-RqwgNa2O.mjs.map → impl-B3bx4FKy.mjs.map} +1 -1
- package/dist/{impl-BvOLkTWD.mjs → impl-B3gPt89q.mjs} +2 -2
- package/dist/{impl-BvOLkTWD.mjs.map → impl-B3gPt89q.mjs.map} +1 -1
- package/dist/{impl-CCSb7Pyd.mjs → impl-B4LIjXnh.mjs} +2 -2
- package/dist/{impl-CCSb7Pyd.mjs.map → impl-B4LIjXnh.mjs.map} +1 -1
- package/dist/{impl-6rvA_yyd.mjs → impl-B6hA8tWQ.mjs} +2 -2
- package/dist/{impl-6rvA_yyd.mjs.map → impl-B6hA8tWQ.mjs.map} +1 -1
- package/dist/{impl-CRSNjo07.mjs → impl-B7HFZI-o.mjs} +2 -2
- package/dist/{impl-CRSNjo07.mjs.map → impl-B7HFZI-o.mjs.map} +1 -1
- package/dist/{impl-DqyYxDti.mjs → impl-BBjnVFyp.mjs} +2 -2
- package/dist/{impl-DqyYxDti.mjs.map → impl-BBjnVFyp.mjs.map} +1 -1
- package/dist/{impl-BOXnkx44.mjs → impl-BEz9kwDB.mjs} +2 -2
- package/dist/{impl-BOXnkx44.mjs.map → impl-BEz9kwDB.mjs.map} +1 -1
- package/dist/{impl-C_K23xoc.mjs → impl-BlKCGB2H.mjs} +2 -2
- package/dist/{impl-C_K23xoc.mjs.map → impl-BlKCGB2H.mjs.map} +1 -1
- package/dist/{impl-X13pzYJN2.mjs → impl-Bopsmew92.mjs} +2 -2
- package/dist/{impl-X13pzYJN2.mjs.map → impl-Bopsmew92.mjs.map} +1 -1
- package/dist/{impl-DK2Rai3F.mjs → impl-BtgbwWAB.mjs} +2 -2
- package/dist/{impl-DK2Rai3F.mjs.map → impl-BtgbwWAB.mjs.map} +1 -1
- package/dist/{impl-BDc4D9nA.mjs → impl-BtipvRbs.mjs} +2 -2
- package/dist/{impl-BDc4D9nA.mjs.map → impl-BtipvRbs.mjs.map} +1 -1
- package/dist/{impl-DQH1CGup.mjs → impl-BzUzbqBO.mjs} +2 -2
- package/dist/{impl-DQH1CGup.mjs.map → impl-BzUzbqBO.mjs.map} +1 -1
- package/dist/{impl-ChDwTQ3Z2.mjs → impl-C6ZYU5m12.mjs} +2 -2
- package/dist/{impl-ChDwTQ3Z2.mjs.map → impl-C6ZYU5m12.mjs.map} +1 -1
- package/dist/{impl-Bq-tabdZ2.mjs → impl-CAftr82F2.mjs} +2 -2
- package/dist/{impl-Bq-tabdZ2.mjs.map → impl-CAftr82F2.mjs.map} +1 -1
- package/dist/{impl-C0I8JaaR.mjs → impl-CCP1-ZeX.mjs} +2 -2
- package/dist/{impl-C0I8JaaR.mjs.map → impl-CCP1-ZeX.mjs.map} +1 -1
- package/dist/{impl-BcIZos3y.mjs → impl-CDpONRWz.mjs} +2 -2
- package/dist/{impl-BcIZos3y.mjs.map → impl-CDpONRWz.mjs.map} +1 -1
- package/dist/{impl-BbfuEhKu.mjs → impl-CGOLSFzA.mjs} +2 -2
- package/dist/{impl-BbfuEhKu.mjs.map → impl-CGOLSFzA.mjs.map} +1 -1
- package/dist/{impl-DMbgck0i.mjs → impl-Cea7k4on.mjs} +2 -2
- package/dist/{impl-DMbgck0i.mjs.map → impl-Cea7k4on.mjs.map} +1 -1
- package/dist/impl-DF1uDAp0.mjs +2 -0
- package/dist/impl-DF1uDAp0.mjs.map +1 -0
- package/dist/{impl-Dl-XNXfK.mjs → impl-DGBJkbeS.mjs} +2 -2
- package/dist/{impl-Dl-XNXfK.mjs.map → impl-DGBJkbeS.mjs.map} +1 -1
- package/dist/{impl-CsHfqKxU.mjs → impl-DOGMbdAR.mjs} +2 -2
- package/dist/{impl-CsHfqKxU.mjs.map → impl-DOGMbdAR.mjs.map} +1 -1
- package/dist/{impl-BuqBEZCk.mjs → impl-DXaDDpI7.mjs} +2 -2
- package/dist/{impl-BuqBEZCk.mjs.map → impl-DXaDDpI7.mjs.map} +1 -1
- package/dist/{impl-CLrF-Xhp2.mjs → impl-D_cEbtMo2.mjs} +2 -2
- package/dist/{impl-CLrF-Xhp2.mjs.map → impl-D_cEbtMo2.mjs.map} +1 -1
- package/dist/{impl-CDNTdi62.mjs → impl-Da2Aoswt.mjs} +2 -2
- package/dist/{impl-CDNTdi62.mjs.map → impl-Da2Aoswt.mjs.map} +1 -1
- package/dist/{impl-B9AtL5ta2.mjs → impl-DcSsXSNW2.mjs} +2 -2
- package/dist/{impl-B9AtL5ta2.mjs.map → impl-DcSsXSNW2.mjs.map} +1 -1
- package/dist/{impl-CkU-WD3b.mjs → impl-DefbnFrT.mjs} +2 -2
- package/dist/{impl-CkU-WD3b.mjs.map → impl-DefbnFrT.mjs.map} +1 -1
- package/dist/{impl-DYsJKLfp.mjs → impl-Dh9KPYhQ.mjs} +2 -2
- package/dist/{impl-DYsJKLfp.mjs.map → impl-Dh9KPYhQ.mjs.map} +1 -1
- package/dist/{impl-BUF6G3P32.mjs → impl-Djk5Ykxb2.mjs} +2 -2
- package/dist/{impl-BUF6G3P32.mjs.map → impl-Djk5Ykxb2.mjs.map} +1 -1
- package/dist/{impl-CX1J2iHE.mjs → impl-Dn7OFlxh.mjs} +2 -2
- package/dist/{impl-CX1J2iHE.mjs.map → impl-Dn7OFlxh.mjs.map} +1 -1
- package/dist/{impl-CkFqqhQ-.mjs → impl-GJ3nq4P6.mjs} +2 -2
- package/dist/{impl-CkFqqhQ-.mjs.map → impl-GJ3nq4P6.mjs.map} +1 -1
- package/dist/{impl-DiysQB762.mjs → impl-OMY9ATKa2.mjs} +2 -2
- package/dist/{impl-DiysQB762.mjs.map → impl-OMY9ATKa2.mjs.map} +1 -1
- package/dist/{impl-TTeKThep2.mjs → impl-SnNbavfi2.mjs} +2 -2
- package/dist/{impl-TTeKThep2.mjs.map → impl-SnNbavfi2.mjs.map} +1 -1
- package/dist/{impl-Bvkhu8fB.mjs → impl-Wu5usA1Y.mjs} +2 -2
- package/dist/{impl-Bvkhu8fB.mjs.map → impl-Wu5usA1Y.mjs.map} +1 -1
- package/dist/{impl-CeqMxdZm2.mjs → impl-YySyd8Le2.mjs} +2 -2
- package/dist/{impl-CeqMxdZm2.mjs.map → impl-YySyd8Le2.mjs.map} +1 -1
- package/dist/{impl-0C9N9md8.mjs → impl-d0CyctvS.mjs} +2 -2
- package/dist/{impl-0C9N9md8.mjs.map → impl-d0CyctvS.mjs.map} +1 -1
- package/dist/{impl-Bul8gUqq.mjs → impl-mKyN6ET8.mjs} +2 -2
- package/dist/{impl-Bul8gUqq.mjs.map → impl-mKyN6ET8.mjs.map} +1 -1
- package/dist/index.d.mts +29 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{markRequestDataSiloIdsCompleted-nAV0VLEA.mjs → markRequestDataSiloIdsCompleted-DXFBaJTl.mjs} +2 -2
- package/dist/{markRequestDataSiloIdsCompleted-nAV0VLEA.mjs.map → markRequestDataSiloIdsCompleted-DXFBaJTl.mjs.map} +1 -1
- package/dist/{markSilentPrivacyRequests-BhF4r8n9.mjs → markSilentPrivacyRequests-ChvSN2zw.mjs} +2 -2
- package/dist/{markSilentPrivacyRequests-BhF4r8n9.mjs.map → markSilentPrivacyRequests-ChvSN2zw.mjs.map} +1 -1
- package/dist/{notifyPrivacyRequestsAdditionalTime-B6e9SfP7.mjs → notifyPrivacyRequestsAdditionalTime-BMXmHJQy.mjs} +2 -2
- package/dist/{notifyPrivacyRequestsAdditionalTime-B6e9SfP7.mjs.map → notifyPrivacyRequestsAdditionalTime-BMXmHJQy.mjs.map} +1 -1
- package/dist/parseVariablesFromString-CrzXJu3n.mjs +3 -0
- package/dist/parseVariablesFromString-CrzXJu3n.mjs.map +1 -0
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-DiZTo2K_.mjs → pullChunkedCustomSiloOutstandingIdentifiers-VRZvZwup.mjs} +2 -2
- package/dist/{pullChunkedCustomSiloOutstandingIdentifiers-DiZTo2K_.mjs.map → pullChunkedCustomSiloOutstandingIdentifiers-VRZvZwup.mjs.map} +1 -1
- package/dist/{pullManualEnrichmentIdentifiersToCsv--dw03arC.mjs → pullManualEnrichmentIdentifiersToCsv-BzVcZB_9.mjs} +2 -2
- package/dist/{pullManualEnrichmentIdentifiersToCsv--dw03arC.mjs.map → pullManualEnrichmentIdentifiersToCsv-BzVcZB_9.mjs.map} +1 -1
- package/dist/pullTranscendConfiguration-CZD-sx8L.mjs +58 -0
- package/dist/pullTranscendConfiguration-CZD-sx8L.mjs.map +1 -0
- package/dist/{pushCronIdentifiersFromCsv-DdcsnXf5.mjs → pushCronIdentifiersFromCsv-nVaVVfm2.mjs} +2 -2
- package/dist/{pushCronIdentifiersFromCsv-DdcsnXf5.mjs.map → pushCronIdentifiersFromCsv-nVaVVfm2.mjs.map} +1 -1
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-BYJCQOI-.mjs → pushManualEnrichmentIdentifiersFromCsv-ChJ0dCPU.mjs} +2 -2
- package/dist/{pushManualEnrichmentIdentifiersFromCsv-BYJCQOI-.mjs.map → pushManualEnrichmentIdentifiersFromCsv-ChJ0dCPU.mjs.map} +1 -1
- package/dist/{readTranscendYaml-I64TqeSd.mjs → readTranscendYaml-B2Asm9c5.mjs} +2 -2
- package/dist/{readTranscendYaml-I64TqeSd.mjs.map → readTranscendYaml-B2Asm9c5.mjs.map} +1 -1
- package/dist/{removeUnverifiedRequestIdentifiers-CC471vSR.mjs → removeUnverifiedRequestIdentifiers-xejbAnmv.mjs} +2 -2
- package/dist/{removeUnverifiedRequestIdentifiers-CC471vSR.mjs.map → removeUnverifiedRequestIdentifiers-xejbAnmv.mjs.map} +1 -1
- package/dist/{retryRequestDataSilos-DlkJmEjH.mjs → retryRequestDataSilos-D9v-Ydk6.mjs} +2 -2
- package/dist/{retryRequestDataSilos-DlkJmEjH.mjs.map → retryRequestDataSilos-D9v-Ydk6.mjs.map} +1 -1
- package/dist/{skipPreflightJobs-xVBZC-iI.mjs → skipPreflightJobs-CAyRUXNn.mjs} +2 -2
- package/dist/{skipPreflightJobs-xVBZC-iI.mjs.map → skipPreflightJobs-CAyRUXNn.mjs.map} +1 -1
- package/dist/{skipRequestDataSilos-BriqhLT3.mjs → skipRequestDataSilos-DNHaLKzO.mjs} +2 -2
- package/dist/{skipRequestDataSilos-BriqhLT3.mjs.map → skipRequestDataSilos-DNHaLKzO.mjs.map} +1 -1
- package/dist/{streamPrivacyRequestsToCsv-D8b097c1.mjs → streamPrivacyRequestsToCsv-DK1t54C2.mjs} +2 -2
- package/dist/{streamPrivacyRequestsToCsv-D8b097c1.mjs.map → streamPrivacyRequestsToCsv-DK1t54C2.mjs.map} +1 -1
- package/dist/{updateConsentManagerVersionToLatest-w_ftOmxi.mjs → updateConsentManagerVersionToLatest-CQ5OaS-T.mjs} +2 -2
- package/dist/{updateConsentManagerVersionToLatest-w_ftOmxi.mjs.map → updateConsentManagerVersionToLatest-CQ5OaS-T.mjs.map} +1 -1
- package/dist/{uploadConsents-D8IJDaTN.mjs → uploadConsents-CG9C7chh.mjs} +2 -2
- package/dist/{uploadConsents-D8IJDaTN.mjs.map → uploadConsents-CG9C7chh.mjs.map} +1 -1
- package/dist/{uploadCookiesFromCsv-8hq-Of_p.mjs → uploadCookiesFromCsv-hcAHtlDf.mjs} +2 -2
- package/dist/{uploadCookiesFromCsv-8hq-Of_p.mjs.map → uploadCookiesFromCsv-hcAHtlDf.mjs.map} +1 -1
- package/dist/{uploadDataFlowsFromCsv-B854o0O5.mjs → uploadDataFlowsFromCsv-B61qZpKb.mjs} +2 -2
- package/dist/{uploadDataFlowsFromCsv-B854o0O5.mjs.map → uploadDataFlowsFromCsv-B61qZpKb.mjs.map} +1 -1
- package/dist/{uploadPrivacyRequestsFromCsv-D-RTgJsx.mjs → uploadPrivacyRequestsFromCsv-DFKHnc0H.mjs} +2 -2
- package/dist/{uploadPrivacyRequestsFromCsv-D-RTgJsx.mjs.map → uploadPrivacyRequestsFromCsv-DFKHnc0H.mjs.map} +1 -1
- package/dist/{validateTranscendAuth-CToEUu1U.mjs → validateTranscendAuth-DrVM-Bxh.mjs} +2 -2
- package/dist/{validateTranscendAuth-CToEUu1U.mjs.map → validateTranscendAuth-DrVM-Bxh.mjs.map} +1 -1
- package/package.json +4 -4
- package/dist/codecs-DW8SY77H.mjs +0 -2
- package/dist/constants-B7bW_WN2.mjs +0 -2
- package/dist/constants-B7bW_WN2.mjs.map +0 -1
- package/dist/enums-CyFTrzXY.mjs.map +0 -1
- package/dist/impl-e-_DWiv4.mjs +0 -2
- package/dist/impl-e-_DWiv4.mjs.map +0 -1
- package/dist/parseVariablesFromString-Cpjk0zeN.mjs +0 -3
- package/dist/parseVariablesFromString-Cpjk0zeN.mjs.map +0 -1
- package/dist/pullTranscendConfiguration-CICvJuZ5.mjs +0 -58
- package/dist/pullTranscendConfiguration-CICvJuZ5.mjs.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=skipRequestDataSilos-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{RequestStatus as n}from"@transcend-io/privacy-types";import r from"colors";import{CHANGE_REQUEST_DATA_SILO_STATUS as i,buildTranscendGraphQLClient as a,fetchRequestDataSilos as o,fetchRequestDataSilosCount as s,makeGraphQLRequest as c}from"@transcend-io/sdk";import{map as l}from"@transcend-io/utils";import u from"cli-progress";async function d({dataSiloId:d,auth:f,concurrency:p=50,status:m=`SKIPPED`,transcendUrl:h=e,requestStatuses:g=[n.Compiling,n.Secondary],actionTypes:_=[]}){let v=a(h,f),y=new Date().getTime(),b=await s(v,{logger:t,filterBy:{dataSiloId:d,requestStatuses:g}});t.info(r.magenta(`Marking ${b} request data silos as completed${_.length>0?` for action types: ${_.join(`,`)}`:``}`));let x=new u.SingleBar({},u.Presets.shades_classic),S=0;x.start(b,0),await l(await o(v,{logger:t,filterBy:{dataSiloId:d,requestStatuses:g},onProgress:e=>{S+=e/2,x.update(S)}}),async e=>{if(_.length===0||_.includes(e.request.type))try{await c(v,i,{variables:{requestDataSiloId:e.id,status:m},logger:t})}catch(e){if(!e.message.includes(`Client error: Request must be active:`))throw e}S+=.5,x.update(S)},{concurrency:p}),x.stop();let C=new Date().getTime()-y;return t.info(r.green(`Successfully skipped "${b}" requests in "${C/1e3}" seconds!`)),b}export{d as t};
|
|
2
|
+
//# sourceMappingURL=skipRequestDataSilos-DNHaLKzO.mjs.map
|
package/dist/{skipRequestDataSilos-BriqhLT3.mjs.map → skipRequestDataSilos-DNHaLKzO.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skipRequestDataSilos-
|
|
1
|
+
{"version":3,"file":"skipRequestDataSilos-DNHaLKzO.mjs","names":[],"sources":["../src/lib/requests/skipRequestDataSilos.ts"],"sourcesContent":["import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n makeGraphQLRequest,\n CHANGE_REQUEST_DATA_SILO_STATUS,\n fetchRequestDataSilos,\n fetchRequestDataSilosCount,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\n\n/**\n * Given a data silo ID, mark all open request data silos as skipped\n *\n * @param options - Options\n * @returns Number of items skipped\n */\nexport async function skipRequestDataSilos({\n dataSiloId,\n auth,\n concurrency = 50,\n status = 'SKIPPED',\n transcendUrl = DEFAULT_TRANSCEND_API,\n requestStatuses = [RequestStatus.Compiling, RequestStatus.Secondary],\n actionTypes = [],\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Data Silo ID to pull down jobs for */\n dataSiloId: string;\n /** Status to set */\n status?: 'SKIPPED' | 'RESOLVED';\n /** Upload concurrency */\n concurrency?: number;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Request statuses to mark as completed */\n requestStatuses?: RequestStatus[];\n /** Request action types to filter on */\n actionTypes?: RequestAction[];\n}): Promise<number> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Time duration\n const t0 = new Date().getTime();\n\n // Determine total number of request data silos\n const requestDataSiloCount = await fetchRequestDataSilosCount(client, {\n logger,\n filterBy: { dataSiloId, requestStatuses },\n });\n logger.info(\n colors.magenta(\n `Marking ${requestDataSiloCount} request data silos as completed${actionTypes.length > 0 ? ` for action types: ${actionTypes.join(',')}` : ''}`,\n ),\n );\n\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n let total = 0;\n progressBar.start(requestDataSiloCount, 0);\n\n // Fetch all matching request data silos, updating progress as pages are fetched\n const requestDataSilos = await fetchRequestDataSilos(client, {\n logger,\n filterBy: { dataSiloId, requestStatuses },\n onProgress: (numFetched) => {\n total += numFetched / 2;\n progressBar.update(total);\n },\n });\n\n await map(\n requestDataSilos,\n async (requestDataSilo) => {\n if (actionTypes.length === 0 || actionTypes.includes(requestDataSilo.request.type)) {\n try {\n await makeGraphQLRequest<{\n /** Whether we successfully uploaded the results */\n success: boolean;\n }>(client, CHANGE_REQUEST_DATA_SILO_STATUS, {\n variables: { requestDataSiloId: requestDataSilo.id, status },\n logger,\n });\n } catch (err) {\n if (!err.message.includes('Client error: Request must be active:')) {\n throw err;\n }\n }\n }\n\n total += 0.5;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully skipped \"${requestDataSiloCount}\" requests in \"${totalTime / 1000}\" seconds!`,\n ),\n );\n return requestDataSiloCount;\n}\n"],"mappings":"kaAqBA,eAAsB,EAAqB,CACzC,aACA,OACA,cAAc,GACd,SAAS,UACT,eAAe,EACf,kBAAkB,CAAC,EAAc,UAAW,EAAc,UAAU,CACpE,cAAc,EAAE,EAgBE,CAElB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAK,IAAI,MAAM,CAAC,SAAS,CAGzB,EAAuB,MAAM,EAA2B,EAAQ,CACpE,SACA,SAAU,CAAE,aAAY,kBAAiB,CAC1C,CAAC,CACF,EAAO,KACL,EAAO,QACL,WAAW,EAAqB,kCAAkC,EAAY,OAAS,EAAI,sBAAsB,EAAY,KAAK,IAAI,GAAK,KAC5I,CACF,CAGD,IAAM,EAAc,IAAI,EAAY,UAAU,EAAE,CAAE,EAAY,QAAQ,eAAe,CAEjF,EAAQ,EACZ,EAAY,MAAM,EAAsB,EAAE,CAY1C,MAAM,EACJ,MAV6B,EAAsB,EAAQ,CAC3D,SACA,SAAU,CAAE,aAAY,kBAAiB,CACzC,WAAa,GAAe,CAC1B,GAAS,EAAa,EACtB,EAAY,OAAO,EAAM,EAE5B,CAAC,CAIA,KAAO,IAAoB,CACzB,GAAI,EAAY,SAAW,GAAK,EAAY,SAAS,EAAgB,QAAQ,KAAK,CAChF,GAAI,CACF,MAAM,EAGH,EAAQ,EAAiC,CAC1C,UAAW,CAAE,kBAAmB,EAAgB,GAAI,SAAQ,CAC5D,SACD,CAAC,OACK,EAAK,CACZ,GAAI,CAAC,EAAI,QAAQ,SAAS,wCAAwC,CAChE,MAAM,EAKZ,GAAS,GACT,EAAY,OAAO,EAAM,EAE3B,CAAE,cAAa,CAChB,CAED,EAAY,MAAM,CAElB,IAAM,EADK,IAAI,MAAM,CAAC,SACF,CAAG,EAOvB,OALA,EAAO,KACL,EAAO,MACL,yBAAyB,EAAqB,iBAAiB,EAAY,IAAK,YACjF,CACF,CACM"}
|
package/dist/{streamPrivacyRequestsToCsv-D8b097c1.mjs → streamPrivacyRequestsToCsv-DK1t54C2.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=streamPrivacyRequestsToCsv-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{i as n,r}from"./fetchAllRequests-rDVYKbqL.mjs";import{i,r as a,t as o}from"./writeCsv-Dg4Wuj5V.mjs";import{groupBy as s,uniq as c}from"lodash-es";import l from"colors";import{buildTranscendGraphQLClient as u,createSombraGotInstance as d,fetchAllRequestIdentifiers as f,validateSombraVersion as p}from"@transcend-io/sdk";import{map as m}from"@transcend-io/utils";import h from"cli-progress";function g({attributeValues:e,requestIdentifiers:t,id:n,email:r,type:i,status:a,subjectType:o,details:c,createdAt:l,successfullyCompletedAt:u,country:d,locale:f,origin:p,countrySubDivision:m,isSilent:h,isTest:g,coreIdentifier:_,purpose:v,...y}){return{"Request ID":n,"Created At":l,"Successfully Completed At":u||``,Email:r,"Core Identifier":_,"Request Type":i,"Data Subject Type":o,Status:a,Country:d,"Country Sub Division":m,Details:c,Origin:p,"Silent Mode":h,"Is Test Request":g,Language:f,"Purpose Trigger Name":v?.title||v?.name||``,"Purpose Trigger Value":v?.consent?.toString()||``,...(v?.enrichedPreferences||[]).reduce((e,t)=>{let n=t.preferenceTopic?.title.defaultMessage||t.name;return n?{...e,[n]:t.selectValues?t.selectValues.map(e=>e.name).join(`;`):t.selectValue?.name||t.booleanValue}:e},{}),...y,...Object.entries(s(e,`attributeKey.name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({name:e})=>e).join(`,`)}),{}),...Object.entries(s(t,`name`)).reduce((e,[t,n])=>Object.assign(e,{[t]:n.map(({value:e})=>e).join(`,`)}),{})}}function _(e,t,n){let r=e.getTime(),i=t.getTime(),a=(i-r)/n;return Array.from({length:n},(e,t)=>({createdAtAfter:new Date(r+a*t),createdAtBefore:new Date(t===n-1?i:r+a*(t+1))}))}async function v({auth:s,sombraAuth:v,actions:y=[],statuses:b=[],identifierSearch:x,concurrency:S=1,pageLimit:C=100,transcendUrl:w=e,createdAtBefore:T,createdAtAfter:E,updatedAtBefore:D,updatedAtAfter:O,isTest:k,skipRequestIdentifiers:A=!1,file:j}){let M=u(w,s),N=A?void 0:await d(w,s,{logger:t,sombraApiKey:v,sombraUrl:process.env.SOMBRA_URL}),P=``;T&&(P+=` before ${T.toISOString()}`),E&&(P+=`${P?`, and`:``} after ${E.toISOString()}`),t.info(l.magenta(`${y.length>0?`Pulling requests of type "${y.join(`" , "`)}"`:`Pulling all requests`}${P}`));let F=S>1&&E&&T,I=F?_(E,T,S):[{createdAtAfter:E,createdAtBefore:T}];F&&t.info(l.magenta(`Splitting date range into ${S} parallel chunks`));let L={type:y.length>0?y:void 0,status:b.length>0?b:void 0,isTest:k,createdAtBefore:T?T.toISOString():void 0,createdAtAfter:E?E.toISOString():void 0,updatedAtBefore:D?D.toISOString():void 0,updatedAtAfter:O?O.toISOString():void 0},R=Date.now();A||await p(M,{logger:t});let z=await n(M,L);t.info(l.magenta(`Fetching ${z} requests`));let B=new h.SingleBar({},h.Presets.shades_classic);B.start(z,0);let V=0,{baseName:H,extension:U}=i(j),W=I.map((e,t)=>I.length===1?j:`${H}-${t}${U}`),G=[],K=await m(I,async(e,n)=>{let i=W[n],s,u=0;try{await r(M,{actions:y,text:x,statuses:b,createdAtBefore:e.createdAtBefore,createdAtAfter:e.createdAtAfter,updatedAtBefore:D,updatedAtAfter:O,isTest:k,onPage:async e=>{if(e.length===0)return;let n=(A?e.map(e=>({...e,requestIdentifiers:[]})):await m(e,async e=>({...e,requestIdentifiers:await f(M,N,{filterBy:{requestId:e.id},skipSombraCheck:!0,logger:t})}),{concurrency:C})).map(g);s||(s=c(n.map(e=>Object.keys(e)).flat()),a(i,s)),o(i,n,s),u+=n.length,V+=n.length,B.update(V)}})}catch(r){let i=r instanceof Error?r.message:String(r);t.error(l.red(`Chunk ${n} failed (${e.createdAtAfter?.toISOString()??`start`} → ${e.createdAtBefore?.toISOString()??`end`}): ${i}`)),G.push({index:n,createdAtAfter:e.createdAtAfter,createdAtBefore:e.createdAtBefore,error:i})}return s||a(i,[]),u},{concurrency:F?S:1});B.stop();let q=K.reduce((e,t)=>e+t,0),J=(Date.now()-R)/1e3;if(G.length>0){t.error(l.red(`\n${G.length} chunk(s) failed. Re-run with these date ranges to fill the gaps:`));for(let e of G)t.error(l.red(` Chunk ${e.index}: --createdAtAfter=${e.createdAtAfter?.toISOString()??``} --createdAtBefore=${e.createdAtBefore?.toISOString()??``}`))}return t.info(l.green(`Streamed ${q} requests to ${W.length} file(s) in ${J}s`)),{filePaths:W,totalCount:q}}export{_ as n,g as r,v as t};
|
|
2
|
+
//# sourceMappingURL=streamPrivacyRequestsToCsv-DK1t54C2.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"streamPrivacyRequestsToCsv-D8b097c1.mjs","names":[],"sources":["../src/lib/requests/formatRequestForCsv.ts","../src/lib/requests/splitDateRange.ts","../src/lib/requests/streamPrivacyRequestsToCsv.ts"],"sourcesContent":["import type { RequestIdentifier } from '@transcend-io/sdk';\nimport { groupBy } from 'lodash-es';\n\nimport type { PrivacyRequest } from '../graphql/index.js';\n\nexport interface ExportedPrivacyRequest extends PrivacyRequest {\n /** Request identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/** A single CSV row */\nexport type CsvRow = { [k in string]: string | null | number | boolean };\n\n/**\n * Format a single privacy request (with optional identifiers) into a flat CSV row.\n *\n * @param request - The request with identifiers attached\n * @returns Flat object suitable for CSV output\n */\nexport function formatRequestForCsv({\n attributeValues,\n requestIdentifiers,\n id,\n email,\n type,\n status,\n subjectType,\n details,\n createdAt,\n successfullyCompletedAt,\n country,\n locale,\n origin,\n countrySubDivision,\n isSilent,\n isTest,\n coreIdentifier,\n purpose,\n ...request\n}: ExportedPrivacyRequest): CsvRow {\n return {\n 'Request ID': id,\n 'Created At': createdAt,\n 'Successfully Completed At': successfullyCompletedAt || '',\n Email: email,\n 'Core Identifier': coreIdentifier,\n 'Request Type': type,\n 'Data Subject Type': subjectType,\n Status: status,\n Country: country,\n 'Country Sub Division': countrySubDivision,\n Details: details,\n Origin: origin,\n 'Silent Mode': isSilent,\n 'Is Test Request': isTest,\n Language: locale,\n 'Purpose Trigger Name': purpose?.title || purpose?.name || '',\n 'Purpose Trigger Value': purpose?.consent?.toString() || '',\n ...(purpose?.enrichedPreferences || []).reduce((acc: Record<string, string | boolean>, p) => {\n const title = p.preferenceTopic?.title.defaultMessage || p.name;\n return title\n ? {\n ...acc,\n [title]: p.selectValues\n ? p.selectValues.map((x) => x.name).join(';')\n : p.selectValue?.name || p.booleanValue,\n }\n : acc;\n }, {}),\n ...request,\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [name, values]) =>\n Object.assign(acc, {\n [name]: values.map(({ name: n }) => n).join(','),\n }),\n {},\n ),\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [name, values]) =>\n Object.assign(acc, {\n [name]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n };\n}\n","export interface ChunkedDateRange {\n /** Chunk start */\n createdAtAfter: Date;\n /** Chunk end */\n createdAtBefore: Date;\n}\n\n/**\n * Split a date range into N evenly-spaced chunks.\n *\n * @param after - Start of the date range\n * @param before - End of the date range\n * @param chunks - Number of chunks to split into\n * @returns Array of date range bounds\n */\nexport function splitDateRange(after: Date, before: Date, chunks: number): ChunkedDateRange[] {\n const startMs = after.getTime();\n const endMs = before.getTime();\n const chunkSizeMs = (endMs - startMs) / chunks;\n return Array.from({ length: chunks }, (_, i) => ({\n createdAtAfter: new Date(startMs + chunkSizeMs * i),\n createdAtBefore: new Date(i === chunks - 1 ? endMs : startMs + chunkSizeMs * (i + 1)),\n }));\n}\n","import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport { uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests, fetchRequestsTotalCount } from '../graphql/index.js';\nimport { initCsvFile, appendCsvRowsOrdered, parseFilePath } from '../helpers/index.js';\nimport { formatRequestForCsv, ExportedPrivacyRequest } from './formatRequestForCsv.js';\nimport { splitDateRange } from './splitDateRange.js';\n\n/**\n * Stream privacy requests directly to CSV files, one file per date-range chunk.\n * Memory stays bounded to a single page of results at a time.\n * Supports both with and without request identifier enrichment.\n *\n * @param options - Options\n * @returns The list of written file paths and total row count\n */\nexport async function streamPrivacyRequestsToCsv({\n auth,\n sombraAuth,\n actions = [],\n statuses = [],\n identifierSearch,\n concurrency = 1,\n pageLimit = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n createdAtBefore,\n createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isTest,\n skipRequestIdentifiers = false,\n file,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Search for a specific identifier */\n identifierSearch?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Statuses to filter on */\n statuses?: RequestStatus[];\n /** The request action to fetch */\n actions?: RequestAction[];\n /** Number of parallel date-range chunks */\n concurrency?: number;\n /** Concurrency for fetching identifiers per page */\n pageLimit?: number;\n /** Filter for requests created before this date */\n createdAtBefore?: Date;\n /** Filter for requests created after this date */\n createdAtAfter?: Date;\n /** Filter for requests updated before this date */\n updatedAtBefore?: Date;\n /** Filter for requests updated after this date */\n updatedAtAfter?: Date;\n /** Return test requests */\n isTest?: boolean;\n /** Skip fetching request identifiers */\n skipRequestIdentifiers?: boolean;\n /** Output CSV file path */\n file: string;\n}): Promise<{\n /** Paths to written CSV files */\n filePaths: string[];\n /** Total rows written */\n totalCount: number;\n}> {\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = skipRequestIdentifiers\n ? undefined\n : await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Log date range\n let dateRange = '';\n if (createdAtBefore) {\n dateRange += ` before ${createdAtBefore.toISOString()}`;\n }\n if (createdAtAfter) {\n dateRange += `${dateRange ? ', and' : ''} after ${createdAtAfter.toISOString()}`;\n }\n logger.info(\n colors.magenta(\n `${\n actions.length > 0\n ? `Pulling requests of type \"${actions.join('\" , \"')}\"`\n : 'Pulling all requests'\n }${dateRange}`,\n ),\n );\n\n // Split into parallel date-range chunks when possible\n const useChunks = concurrency > 1 && createdAtAfter && createdAtBefore;\n const chunks = useChunks\n ? splitDateRange(createdAtAfter, createdAtBefore, concurrency)\n : [{ createdAtAfter, createdAtBefore }];\n\n if (useChunks) {\n logger.info(colors.magenta(`Splitting date range into ${concurrency} parallel chunks`));\n }\n\n // Fetch total count once for the shared progress bar\n const filterBy = {\n type: actions.length > 0 ? actions : undefined,\n status: statuses.length > 0 ? statuses : undefined,\n isTest,\n createdAtBefore: createdAtBefore ? createdAtBefore.toISOString() : undefined,\n createdAtAfter: createdAtAfter ? createdAtAfter.toISOString() : undefined,\n updatedAtBefore: updatedAtBefore ? updatedAtBefore.toISOString() : undefined,\n updatedAtAfter: updatedAtAfter ? updatedAtAfter.toISOString() : undefined,\n };\n\n const t0 = Date.now();\n\n // Validate Sombra version once before bulk-fetching identifiers\n if (!skipRequestIdentifiers) {\n await validateSombraVersion(client, { logger });\n }\n\n const totalExpected = await fetchRequestsTotalCount(client, filterBy);\n logger.info(colors.magenta(`Fetching ${totalExpected} requests`));\n\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n progressBar.start(totalExpected, 0);\n\n let globalFetched = 0;\n\n const { baseName, extension } = parseFilePath(file);\n\n const filePaths = chunks.map((_, i) =>\n chunks.length === 1 ? file : `${baseName}-${i}${extension}`,\n );\n\n interface FailedChunk {\n /** Chunk index */\n index: number;\n /** Start of failed date range */\n createdAtAfter?: Date;\n /** End of failed date range */\n createdAtBefore?: Date;\n /** Error message */\n error: string;\n }\n\n const failedChunks: FailedChunk[] = [];\n\n const chunkCounts = await map(\n chunks,\n async (chunk, i) => {\n const chunkFile = filePaths[i];\n let headers: string[] | undefined;\n let rowCount = 0;\n\n try {\n await fetchAllRequests(client, {\n actions,\n text: identifierSearch,\n statuses,\n createdAtBefore: chunk.createdAtBefore,\n createdAtAfter: chunk.createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isTest,\n onPage: async (nodes) => {\n if (nodes.length === 0) return;\n\n // Optionally enrich each request with its identifiers\n const enriched: ExportedPrivacyRequest[] = skipRequestIdentifiers\n ? nodes.map((n) => ({ ...n, requestIdentifiers: [] }))\n : await map(\n nodes,\n async (n) => ({\n ...n,\n requestIdentifiers: await fetchAllRequestIdentifiers(client, sombra!, {\n filterBy: { requestId: n.id },\n skipSombraCheck: true,\n logger,\n }),\n }),\n { concurrency: pageLimit },\n );\n\n const rows: Record<string, string | null | number | boolean>[] =\n enriched.map(formatRequestForCsv);\n\n if (!headers) {\n headers = uniq(rows.map((r: Record<string, unknown>) => Object.keys(r)).flat());\n initCsvFile(chunkFile, headers);\n }\n\n appendCsvRowsOrdered(chunkFile, rows, headers);\n rowCount += rows.length;\n globalFetched += rows.length;\n progressBar.update(globalFetched);\n },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(\n colors.red(\n `Chunk ${i} failed (${\n chunk.createdAtAfter?.toISOString() ?? 'start'\n } → ${chunk.createdAtBefore?.toISOString() ?? 'end'}): ${message}`,\n ),\n );\n failedChunks.push({\n index: i,\n createdAtAfter: chunk.createdAtAfter,\n createdAtBefore: chunk.createdAtBefore,\n error: message,\n });\n }\n\n if (!headers) {\n initCsvFile(chunkFile, []);\n }\n\n return rowCount;\n },\n { concurrency: useChunks ? concurrency : 1 },\n );\n\n progressBar.stop();\n const totalCount = chunkCounts.reduce((a, b) => a + b, 0);\n const elapsed = (Date.now() - t0) / 1000;\n\n if (failedChunks.length > 0) {\n logger.error(\n colors.red(\n `\\n${failedChunks.length} chunk(s) failed. ` +\n 'Re-run with these date ranges to fill the gaps:',\n ),\n );\n for (const fc of failedChunks) {\n logger.error(\n colors.red(\n ` Chunk ${fc.index}: --createdAtAfter=${\n fc.createdAtAfter?.toISOString() ?? ''\n } --createdAtBefore=${fc.createdAtBefore?.toISOString() ?? ''}`,\n ),\n );\n }\n }\n\n logger.info(\n colors.green(`Streamed ${totalCount} requests to ${filePaths.length} file(s) in ${elapsed}s`),\n );\n\n return { filePaths, totalCount };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"streamPrivacyRequestsToCsv-DK1t54C2.mjs","names":[],"sources":["../src/lib/requests/formatRequestForCsv.ts","../src/lib/requests/splitDateRange.ts","../src/lib/requests/streamPrivacyRequestsToCsv.ts"],"sourcesContent":["import type { RequestIdentifier } from '@transcend-io/sdk';\nimport { groupBy } from 'lodash-es';\n\nimport type { PrivacyRequest } from '../graphql/index.js';\n\nexport interface ExportedPrivacyRequest extends PrivacyRequest {\n /** Request identifiers */\n requestIdentifiers: RequestIdentifier[];\n}\n\n/** A single CSV row */\nexport type CsvRow = { [k in string]: string | null | number | boolean };\n\n/**\n * Format a single privacy request (with optional identifiers) into a flat CSV row.\n *\n * @param request - The request with identifiers attached\n * @returns Flat object suitable for CSV output\n */\nexport function formatRequestForCsv({\n attributeValues,\n requestIdentifiers,\n id,\n email,\n type,\n status,\n subjectType,\n details,\n createdAt,\n successfullyCompletedAt,\n country,\n locale,\n origin,\n countrySubDivision,\n isSilent,\n isTest,\n coreIdentifier,\n purpose,\n ...request\n}: ExportedPrivacyRequest): CsvRow {\n return {\n 'Request ID': id,\n 'Created At': createdAt,\n 'Successfully Completed At': successfullyCompletedAt || '',\n Email: email,\n 'Core Identifier': coreIdentifier,\n 'Request Type': type,\n 'Data Subject Type': subjectType,\n Status: status,\n Country: country,\n 'Country Sub Division': countrySubDivision,\n Details: details,\n Origin: origin,\n 'Silent Mode': isSilent,\n 'Is Test Request': isTest,\n Language: locale,\n 'Purpose Trigger Name': purpose?.title || purpose?.name || '',\n 'Purpose Trigger Value': purpose?.consent?.toString() || '',\n ...(purpose?.enrichedPreferences || []).reduce((acc: Record<string, string | boolean>, p) => {\n const title = p.preferenceTopic?.title.defaultMessage || p.name;\n return title\n ? {\n ...acc,\n [title]: p.selectValues\n ? p.selectValues.map((x) => x.name).join(';')\n : p.selectValue?.name || p.booleanValue,\n }\n : acc;\n }, {}),\n ...request,\n ...Object.entries(groupBy(attributeValues, 'attributeKey.name')).reduce(\n (acc, [name, values]) =>\n Object.assign(acc, {\n [name]: values.map(({ name: n }) => n).join(','),\n }),\n {},\n ),\n ...Object.entries(groupBy(requestIdentifiers, 'name')).reduce(\n (acc, [name, values]) =>\n Object.assign(acc, {\n [name]: values.map(({ value }) => value).join(','),\n }),\n {},\n ),\n };\n}\n","export interface ChunkedDateRange {\n /** Chunk start */\n createdAtAfter: Date;\n /** Chunk end */\n createdAtBefore: Date;\n}\n\n/**\n * Split a date range into N evenly-spaced chunks.\n *\n * @param after - Start of the date range\n * @param before - End of the date range\n * @param chunks - Number of chunks to split into\n * @returns Array of date range bounds\n */\nexport function splitDateRange(after: Date, before: Date, chunks: number): ChunkedDateRange[] {\n const startMs = after.getTime();\n const endMs = before.getTime();\n const chunkSizeMs = (endMs - startMs) / chunks;\n return Array.from({ length: chunks }, (_, i) => ({\n createdAtAfter: new Date(startMs + chunkSizeMs * i),\n createdAtBefore: new Date(i === chunks - 1 ? endMs : startMs + chunkSizeMs * (i + 1)),\n }));\n}\n","import { RequestAction, RequestStatus } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n createSombraGotInstance,\n fetchAllRequestIdentifiers,\n validateSombraVersion,\n} from '@transcend-io/sdk';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport { uniq } from 'lodash-es';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { fetchAllRequests, fetchRequestsTotalCount } from '../graphql/index.js';\nimport { initCsvFile, appendCsvRowsOrdered, parseFilePath } from '../helpers/index.js';\nimport { formatRequestForCsv, ExportedPrivacyRequest } from './formatRequestForCsv.js';\nimport { splitDateRange } from './splitDateRange.js';\n\n/**\n * Stream privacy requests directly to CSV files, one file per date-range chunk.\n * Memory stays bounded to a single page of results at a time.\n * Supports both with and without request identifier enrichment.\n *\n * @param options - Options\n * @returns The list of written file paths and total row count\n */\nexport async function streamPrivacyRequestsToCsv({\n auth,\n sombraAuth,\n actions = [],\n statuses = [],\n identifierSearch,\n concurrency = 1,\n pageLimit = 100,\n transcendUrl = DEFAULT_TRANSCEND_API,\n createdAtBefore,\n createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isTest,\n skipRequestIdentifiers = false,\n file,\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** Search for a specific identifier */\n identifierSearch?: string;\n /** Sombra API key authentication */\n sombraAuth?: string;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Statuses to filter on */\n statuses?: RequestStatus[];\n /** The request action to fetch */\n actions?: RequestAction[];\n /** Number of parallel date-range chunks */\n concurrency?: number;\n /** Concurrency for fetching identifiers per page */\n pageLimit?: number;\n /** Filter for requests created before this date */\n createdAtBefore?: Date;\n /** Filter for requests created after this date */\n createdAtAfter?: Date;\n /** Filter for requests updated before this date */\n updatedAtBefore?: Date;\n /** Filter for requests updated after this date */\n updatedAtAfter?: Date;\n /** Return test requests */\n isTest?: boolean;\n /** Skip fetching request identifiers */\n skipRequestIdentifiers?: boolean;\n /** Output CSV file path */\n file: string;\n}): Promise<{\n /** Paths to written CSV files */\n filePaths: string[];\n /** Total rows written */\n totalCount: number;\n}> {\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n const sombra = skipRequestIdentifiers\n ? undefined\n : await createSombraGotInstance(transcendUrl, auth, {\n logger,\n sombraApiKey: sombraAuth,\n sombraUrl: process.env.SOMBRA_URL,\n });\n\n // Log date range\n let dateRange = '';\n if (createdAtBefore) {\n dateRange += ` before ${createdAtBefore.toISOString()}`;\n }\n if (createdAtAfter) {\n dateRange += `${dateRange ? ', and' : ''} after ${createdAtAfter.toISOString()}`;\n }\n logger.info(\n colors.magenta(\n `${\n actions.length > 0\n ? `Pulling requests of type \"${actions.join('\" , \"')}\"`\n : 'Pulling all requests'\n }${dateRange}`,\n ),\n );\n\n // Split into parallel date-range chunks when possible\n const useChunks = concurrency > 1 && createdAtAfter && createdAtBefore;\n const chunks = useChunks\n ? splitDateRange(createdAtAfter, createdAtBefore, concurrency)\n : [{ createdAtAfter, createdAtBefore }];\n\n if (useChunks) {\n logger.info(colors.magenta(`Splitting date range into ${concurrency} parallel chunks`));\n }\n\n // Fetch total count once for the shared progress bar\n const filterBy = {\n type: actions.length > 0 ? actions : undefined,\n status: statuses.length > 0 ? statuses : undefined,\n isTest,\n createdAtBefore: createdAtBefore ? createdAtBefore.toISOString() : undefined,\n createdAtAfter: createdAtAfter ? createdAtAfter.toISOString() : undefined,\n updatedAtBefore: updatedAtBefore ? updatedAtBefore.toISOString() : undefined,\n updatedAtAfter: updatedAtAfter ? updatedAtAfter.toISOString() : undefined,\n };\n\n const t0 = Date.now();\n\n // Validate Sombra version once before bulk-fetching identifiers\n if (!skipRequestIdentifiers) {\n await validateSombraVersion(client, { logger });\n }\n\n const totalExpected = await fetchRequestsTotalCount(client, filterBy);\n logger.info(colors.magenta(`Fetching ${totalExpected} requests`));\n\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n progressBar.start(totalExpected, 0);\n\n let globalFetched = 0;\n\n const { baseName, extension } = parseFilePath(file);\n\n const filePaths = chunks.map((_, i) =>\n chunks.length === 1 ? file : `${baseName}-${i}${extension}`,\n );\n\n interface FailedChunk {\n /** Chunk index */\n index: number;\n /** Start of failed date range */\n createdAtAfter?: Date;\n /** End of failed date range */\n createdAtBefore?: Date;\n /** Error message */\n error: string;\n }\n\n const failedChunks: FailedChunk[] = [];\n\n const chunkCounts = await map(\n chunks,\n async (chunk, i) => {\n const chunkFile = filePaths[i];\n let headers: string[] | undefined;\n let rowCount = 0;\n\n try {\n await fetchAllRequests(client, {\n actions,\n text: identifierSearch,\n statuses,\n createdAtBefore: chunk.createdAtBefore,\n createdAtAfter: chunk.createdAtAfter,\n updatedAtBefore,\n updatedAtAfter,\n isTest,\n onPage: async (nodes) => {\n if (nodes.length === 0) return;\n\n // Optionally enrich each request with its identifiers\n const enriched: ExportedPrivacyRequest[] = skipRequestIdentifiers\n ? nodes.map((n) => ({ ...n, requestIdentifiers: [] }))\n : await map(\n nodes,\n async (n) => ({\n ...n,\n requestIdentifiers: await fetchAllRequestIdentifiers(client, sombra!, {\n filterBy: { requestId: n.id },\n skipSombraCheck: true,\n logger,\n }),\n }),\n { concurrency: pageLimit },\n );\n\n const rows: Record<string, string | null | number | boolean>[] =\n enriched.map(formatRequestForCsv);\n\n if (!headers) {\n headers = uniq(rows.map((r: Record<string, unknown>) => Object.keys(r)).flat());\n initCsvFile(chunkFile, headers);\n }\n\n appendCsvRowsOrdered(chunkFile, rows, headers);\n rowCount += rows.length;\n globalFetched += rows.length;\n progressBar.update(globalFetched);\n },\n });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n logger.error(\n colors.red(\n `Chunk ${i} failed (${\n chunk.createdAtAfter?.toISOString() ?? 'start'\n } → ${chunk.createdAtBefore?.toISOString() ?? 'end'}): ${message}`,\n ),\n );\n failedChunks.push({\n index: i,\n createdAtAfter: chunk.createdAtAfter,\n createdAtBefore: chunk.createdAtBefore,\n error: message,\n });\n }\n\n if (!headers) {\n initCsvFile(chunkFile, []);\n }\n\n return rowCount;\n },\n { concurrency: useChunks ? concurrency : 1 },\n );\n\n progressBar.stop();\n const totalCount = chunkCounts.reduce((a, b) => a + b, 0);\n const elapsed = (Date.now() - t0) / 1000;\n\n if (failedChunks.length > 0) {\n logger.error(\n colors.red(\n `\\n${failedChunks.length} chunk(s) failed. ` +\n 'Re-run with these date ranges to fill the gaps:',\n ),\n );\n for (const fc of failedChunks) {\n logger.error(\n colors.red(\n ` Chunk ${fc.index}: --createdAtAfter=${\n fc.createdAtAfter?.toISOString() ?? ''\n } --createdAtBefore=${fc.createdAtBefore?.toISOString() ?? ''}`,\n ),\n );\n }\n }\n\n logger.info(\n colors.green(`Streamed ${totalCount} requests to ${filePaths.length} file(s) in ${elapsed}s`),\n );\n\n return { filePaths, totalCount };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=updateConsentManagerVersionToLatest-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{ConsentBundleType as n}from"@transcend-io/privacy-types";import r from"colors";import{buildTranscendGraphQLClient as i,deployConsentManager as a,fetchConsentManagerId as o,updateConsentManagerToLatest as s}from"@transcend-io/sdk";import{mapSeries as c}from"@transcend-io/utils";async function l({auth:l,deploy:u=!1,transcendUrl:d=e,bundleTypes:f=Object.values(n)}){let p=i(d,l),m=await o(p,{logger:t});await c(f,async e=>{t.info(r.magenta(`Update Consent Manager bundle with ID "${m}" and type "${e}" to latest version...`)),await s(p,{input:{id:m,bundleType:e},logger:t}),t.info(r.green(`Updated Consent Manager bundle with ID "${m}" and type "${e}" to latest version!`))}),u&&await c(f,async e=>{t.info(r.magenta(`Deploying Consent Manager bundle with ID "${m}" and type "${e}"...`)),await a(p,{id:m,bundleType:e},{logger:t}),t.info(r.green(`Deployed Consent Manager bundle with ID "${m}" and type "${e}"!`))})}export{l as t};
|
|
2
|
+
//# sourceMappingURL=updateConsentManagerVersionToLatest-CQ5OaS-T.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"updateConsentManagerVersionToLatest-
|
|
1
|
+
{"version":3,"file":"updateConsentManagerVersionToLatest-CQ5OaS-T.mjs","names":[],"sources":["../src/lib/consent-manager/updateConsentManagerVersionToLatest.ts"],"sourcesContent":["import { ConsentBundleType } from '@transcend-io/privacy-types';\nimport {\n buildTranscendGraphQLClient,\n deployConsentManager,\n fetchConsentManagerId,\n updateConsentManagerToLatest,\n} from '@transcend-io/sdk';\nimport { mapSeries } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\n\n/**\n * Update the consent manager to latest version\n *\n * @param options - Options\n */\nexport async function updateConsentManagerVersionToLatest({\n auth,\n deploy = false,\n transcendUrl = DEFAULT_TRANSCEND_API,\n bundleTypes = Object.values(ConsentBundleType),\n}: {\n /** Transcend API key authentication */\n auth: string;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Deploy consent manager with this update */\n deploy?: boolean;\n /** The bundle types to update and deploy */\n bundleTypes?: ConsentBundleType[];\n}): Promise<void> {\n // Find all requests made before createdAt that are in a removing data state\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Grab Consent Manager ID\n const consentManagerId = await fetchConsentManagerId(client, { logger });\n\n // Update each bundle type to latest version\n await mapSeries(bundleTypes, async (bundleType) => {\n logger.info(\n colors.magenta(\n `Update Consent Manager bundle with ID \"${consentManagerId}\" and type \"${bundleType}\" to latest version...`,\n ),\n );\n await updateConsentManagerToLatest(client, {\n input: {\n id: consentManagerId,\n bundleType,\n },\n logger,\n });\n logger.info(\n colors.green(\n `Updated Consent Manager bundle with ID \"${consentManagerId}\" and type \"${bundleType}\" to latest version!`,\n ),\n );\n });\n\n // deploy Consent Managers\n if (deploy) {\n // Update each bundle type to latest version\n await mapSeries(bundleTypes, async (bundleType) => {\n logger.info(\n colors.magenta(\n `Deploying Consent Manager bundle with ID \"${consentManagerId}\" and type \"${bundleType}\"...`,\n ),\n );\n await deployConsentManager(\n client,\n {\n id: consentManagerId,\n bundleType,\n },\n { logger },\n );\n logger.info(\n colors.green(\n `Deployed Consent Manager bundle with ID \"${consentManagerId}\" and type \"${bundleType}\"!`,\n ),\n );\n });\n }\n}\n"],"mappings":"+WAkBA,eAAsB,EAAoC,CACxD,OACA,SAAS,GACT,eAAe,EACf,cAAc,OAAO,OAAO,EAAkB,EAU9B,CAEhB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAGxD,EAAmB,MAAM,EAAsB,EAAQ,CAAE,SAAQ,CAAC,CAGxE,MAAM,EAAU,EAAa,KAAO,IAAe,CACjD,EAAO,KACL,EAAO,QACL,0CAA0C,EAAiB,cAAc,EAAW,wBACrF,CACF,CACD,MAAM,EAA6B,EAAQ,CACzC,MAAO,CACL,GAAI,EACJ,aACD,CACD,SACD,CAAC,CACF,EAAO,KACL,EAAO,MACL,2CAA2C,EAAiB,cAAc,EAAW,sBACtF,CACF,EACD,CAGE,GAEF,MAAM,EAAU,EAAa,KAAO,IAAe,CACjD,EAAO,KACL,EAAO,QACL,6CAA6C,EAAiB,cAAc,EAAW,MACxF,CACF,CACD,MAAM,EACJ,EACA,CACE,GAAI,EACJ,aACD,CACD,CAAE,SAAQ,CACX,CACD,EAAO,KACL,EAAO,MACL,4CAA4C,EAAiB,cAAc,EAAW,IACvF,CACF,EACD"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{s as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=uploadConsents-
|
|
1
|
+
import{s as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{decodeCodec as n}from"@transcend-io/type-utils";import r from"colors";import*as i from"io-ts";import{createTranscendConsentGotInstance as a}from"@transcend-io/sdk";import{map as o}from"@transcend-io/utils";import s from"cli-progress";import*as c from"crypto";import l from"jsonwebtoken";function u(e,t,n){let r=Buffer.from(n,`base64`),i=Buffer.from(t,`base64`),a=Buffer.from(`A65959A6`,`hex`),o=c.createCipheriv(`id-aes256-wrap-pad`,i,a),s={encryptedIdentifier:Buffer.concat([o.update(e),o.final()]).toString(`base64`)};return l.sign(s,r,{algorithm:`HS384`})}const d=/^[0-9][Y|N]([Y|N])[Y|N]$/,f=i.record(i.string,i.union([i.boolean,i.literal(`Auto`)]));async function p({base64EncryptionKey:i,base64SigningKey:c,preferences:l,partition:p,concurrency:m=100,transcendUrl:h=e}){let g=a(h),_=l.filter(e=>e.usp&&!d.test(e.usp));if(_.length>0)throw Error(`Received invalid usp strings: ${JSON.stringify(_,null,2)}`);let v=l.map((e,t)=>[e,t]).filter(([e])=>{if(!e.purposes)return!1;try{return n(f,e.purposes),!1}catch{return!0}});if(v.length>0)throw Error(`Received invalid purpose maps: ${JSON.stringify(v,null,2)}`);let y=l.filter(e=>!e.usp&&!e.purposes);if(y.length>0)throw Error(`Received invalid inputs, expected either purposes or usp to be defined: ${JSON.stringify(y,null,2)}`);t.info(r.magenta(`Uploading ${l.length} user preferences to partition ${p}`));let b=new Date().getTime(),x=new s.SingleBar({},s.Presets.shades_classic),S=0;x.start(l.length,0),await o(l,async({userId:e,confirmed:a=`true`,updated:o,prompted:s,purposes:l,...m})=>{let h=u(e,i,c),[,_]=m.usp&&d.exec(m.usp)||[],v={token:h,partition:p,consent:{confirmed:a===`true`,purposes:l?n(f,l):m.usp?{SaleOfInfo:_===`Y`}:{},...o?{updated:o===`true`}:{},...s?{prompted:s===`true`}:{},...m}};try{await g.post(`sync`,{json:v}).json()}catch(e){try{let n=JSON.parse(e?.response?.body||`{}`);n.error&&t.error(r.red(`Error: ${n.error}`))}catch{}throw Error(`Received an error from server: ${e?.response?.body||e?.message}`)}S+=1,x.update(S)},{concurrency:m}),x.stop();let C=new Date().getTime()-b;t.info(r.green(`Successfully uploaded ${l.length} user preferences to partition ${p} in "${C/1e3}" seconds!`))}export{u as i,d as n,p as r,f as t};
|
|
2
|
+
//# sourceMappingURL=uploadConsents-CG9C7chh.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadConsents-
|
|
1
|
+
{"version":3,"file":"uploadConsents-CG9C7chh.mjs","names":[],"sources":["../src/lib/consent-manager/createConsentToken.ts","../src/lib/consent-manager/uploadConsents.ts"],"sourcesContent":["import * as crypto from 'crypto';\n\nimport jwt from 'jsonwebtoken';\n\n/**\n * Function to create a consent manager token\n *\n * @see https://docs.transcend.io/docs/consent/reference/managed-consent-database\n * @param userId - User ID\n * @param base64EncryptionKey - Encryption key\n * @param base64SigningKey - Signing key\n * @returns Token\n */\nexport function createConsentToken(\n userId: string,\n base64EncryptionKey: string,\n base64SigningKey: string,\n): string {\n // Read on for where to find these keys\n const signingKey = Buffer.from(base64SigningKey, 'base64');\n const encryptionKey = Buffer.from(base64EncryptionKey, 'base64');\n\n // NIST's AES-KWP implementation { aes 48 } - see https://tools.ietf.org/html/rfc5649\n const encryptionAlgorithm = 'id-aes256-wrap-pad';\n // Initial Value for AES-KWP integrity check - see https://tools.ietf.org/html/rfc5649#section-3\n const iv = Buffer.from('A65959A6', 'hex');\n // Set up encryption algorithm\n const cipher = crypto.createCipheriv(encryptionAlgorithm, encryptionKey, iv);\n\n // Encrypt the userId and base64-encode the result\n const encryptedIdentifier = Buffer.concat([cipher.update(userId), cipher.final()]).toString(\n 'base64',\n );\n\n // Create the JWT content - jwt.sign will add a 'iat' (issued at) field to the payload\n // If you wanted to add something manually, consider\n // const issued: Date = new Date();\n // const isoDate = issued.toISOString();\n const jwtPayload = {\n encryptedIdentifier,\n };\n\n // Create a JSON web token and HMAC it with SHA-384\n const consentToken = jwt.sign(jwtPayload, signingKey, {\n algorithm: 'HS384',\n });\n\n return consentToken;\n}\n","import { ConsentPreferencesBody } from '@transcend-io/airgap.js-types';\nimport { createTranscendConsentGotInstance } from '@transcend-io/sdk';\nimport { decodeCodec } from '@transcend-io/type-utils';\nimport { map } from '@transcend-io/utils';\nimport cliProgress from 'cli-progress';\nimport colors from 'colors';\nimport * as t from 'io-ts';\n\nimport { DEFAULT_TRANSCEND_CONSENT_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { createConsentToken } from './createConsentToken.js';\nimport type { ConsentPreferenceUpload } from './types.js';\n\nexport const USP_STRING_REGEX = /^[0-9][Y|N]([Y|N])[Y|N]$/;\n\nexport const PurposeMap = t.record(t.string, t.union([t.boolean, t.literal('Auto')]));\n\n/**\n * Upload a set of consent preferences\n *\n * @param options - Options\n */\nexport async function uploadConsents({\n base64EncryptionKey,\n base64SigningKey,\n preferences,\n partition,\n concurrency = 100,\n transcendUrl = DEFAULT_TRANSCEND_CONSENT_API,\n}: {\n /** base64 encryption key */\n base64EncryptionKey: string;\n /** base64 signing key */\n base64SigningKey: string;\n /** Partition key */\n partition: string;\n /** Sombra API key authentication */\n preferences: ConsentPreferenceUpload[];\n /** API URL for Transcend backend */\n transcendUrl?: string;\n /** Concurrency limit for approving */\n concurrency?: number;\n}): Promise<void> {\n // Create connection to API\n const transcendConsentApi = createTranscendConsentGotInstance(transcendUrl);\n\n // Ensure usp strings are valid\n const invalidUspStrings = preferences.filter(\n (pref) => pref.usp && !USP_STRING_REGEX.test(pref.usp),\n );\n if (invalidUspStrings.length > 0) {\n throw new Error(`Received invalid usp strings: ${JSON.stringify(invalidUspStrings, null, 2)}`);\n }\n\n // Ensure purpose maps are valid\n const invalidPurposeMaps = preferences\n .map((pref, ind) => [pref, ind] as [ConsentPreferenceUpload, number])\n .filter(([pref]) => {\n if (!pref.purposes) {\n return false;\n }\n try {\n decodeCodec(PurposeMap, pref.purposes);\n return false;\n } catch {\n return true;\n }\n });\n if (invalidPurposeMaps.length > 0) {\n throw new Error(\n `Received invalid purpose maps: ${JSON.stringify(invalidPurposeMaps, null, 2)}`,\n );\n }\n\n // Ensure usp or preferences are provided\n const invalidInputs = preferences.filter((pref) => !pref.usp && !pref.purposes);\n if (invalidInputs.length > 0) {\n throw new Error(\n `Received invalid inputs, expected either purposes or usp to be defined: ${JSON.stringify(\n invalidInputs,\n null,\n 2,\n )}`,\n );\n }\n\n logger.info(\n colors.magenta(`Uploading ${preferences.length} user preferences to partition ${partition}`),\n );\n\n // Time duration\n const t0 = new Date().getTime();\n // create a new progress bar instance and use shades_classic theme\n const progressBar = new cliProgress.SingleBar({}, cliProgress.Presets.shades_classic);\n\n // Build a GraphQL client\n let total = 0;\n progressBar.start(preferences.length, 0);\n await map(\n preferences,\n async ({ userId, confirmed = 'true', updated, prompted, purposes, ...consent }) => {\n const token = createConsentToken(userId, base64EncryptionKey, base64SigningKey);\n\n // parse usp string\n const [, saleStatus] = consent.usp ? USP_STRING_REGEX.exec(consent.usp) || [] : [];\n\n const input = {\n token,\n partition,\n consent: {\n confirmed: confirmed === 'true',\n purposes: purposes\n ? decodeCodec(PurposeMap, purposes)\n : consent.usp\n ? { SaleOfInfo: saleStatus === 'Y' }\n : {},\n ...(updated ? { updated: updated === 'true' } : {}),\n ...(prompted ? { prompted: prompted === 'true' } : {}),\n ...consent,\n },\n } as ConsentPreferencesBody;\n\n // Make the request\n try {\n await transcendConsentApi\n .post('sync', {\n json: input,\n })\n .json();\n } catch (err) {\n try {\n const parsed = JSON.parse(err?.response?.body || '{}');\n if (parsed.error) {\n logger.error(colors.red(`Error: ${parsed.error}`));\n }\n } catch {\n // continue\n }\n throw new Error(`Received an error from server: ${err?.response?.body || err?.message}`);\n }\n\n total += 1;\n progressBar.update(total);\n },\n { concurrency },\n );\n\n progressBar.stop();\n const t1 = new Date().getTime();\n const totalTime = t1 - t0;\n\n logger.info(\n colors.green(\n `Successfully uploaded ${preferences.length} user preferences to partition ${partition} in \"${\n totalTime / 1000\n }\" seconds!`,\n ),\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=uploadCookiesFromCsv-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{A as n}from"./codecs-CwRYEdQK.mjs";import{t as r}from"./readCsv-ddmavI86.mjs";import i from"colors";import{buildTranscendGraphQLClient as a,syncCookies as o}from"@transcend-io/sdk";import{splitCsvToList as s}from"@transcend-io/utils";const c=[`ID`,`Activity`,`Encounters`,`Last Seen At`,`Has Native Do Not Sell/Share Support`,`IAB USP API Support`,`Service Description`,`Website URL`,`Categories of Recipients`];async function l({auth:l,trackerStatus:u,file:d,transcendUrl:f=e}){let p=a(f,l);t.info(i.magenta(`Reading "${d}" from disk`)),await o(p,r(d,n).map(({"Is Regex?":e,Notes:t,Service:n,Purpose:r,Status:i,Owners:a,Teams:o,Name:l,...d})=>({...typeof e==`string`?{isRegex:e.toLowerCase()===`true`}:{},name:l,description:t,trackingPurposes:s(r),status:i||u,owners:a?s(a):void 0,teams:o?s(o):void 0,attributes:Object.entries(d).filter(([e])=>!c.includes(e)).map(([e,t])=>({key:e,values:s(t)}))})),{logger:t})||(t.error(i.red(`Encountered error(s) syncing cookies from CSV, see logs above for more info. `)),process.exit(1))}export{l as t};
|
|
2
|
+
//# sourceMappingURL=uploadCookiesFromCsv-hcAHtlDf.mjs.map
|
package/dist/{uploadCookiesFromCsv-8hq-Of_p.mjs.map → uploadCookiesFromCsv-hcAHtlDf.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadCookiesFromCsv-
|
|
1
|
+
{"version":3,"file":"uploadCookiesFromCsv-hcAHtlDf.mjs","names":[],"sources":["../src/lib/consent-manager/uploadCookiesFromCsv.ts"],"sourcesContent":["import { ConsentTrackerStatus } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, syncCookies } from '@transcend-io/sdk';\nimport { splitCsvToList } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { CookieInput, CookieCsvInput } from '../../codecs.js';\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { readCsv } from '../requests/readCsv.js';\n\nconst OMIT_COLUMNS = [\n 'ID',\n 'Activity',\n 'Encounters',\n 'Last Seen At',\n 'Has Native Do Not Sell/Share Support',\n 'IAB USP API Support',\n 'Service Description',\n 'Website URL',\n 'Categories of Recipients',\n];\n\n/**\n * Upload a set of cookies from CSV\n *\n * @param options - Options\n */\nexport async function uploadCookiesFromCsv({\n auth,\n trackerStatus,\n file,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key authentication */\n trackerStatus: ConsentTrackerStatus;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<void> {\n // Build a GraphQL client\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Read from CSV the set of cookie inputs\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const cookieInputs = readCsv(file, CookieCsvInput);\n\n // Convert these inputs into a format that the other function can use\n const validatedCookieInputs = cookieInputs.map(\n ({\n 'Is Regex?': isRegex,\n Notes,\n // TODO: https://transcend.height.app/T-26391 - export in CSV\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Service,\n Purpose,\n Status,\n Owners,\n Teams,\n Name,\n ...rest\n }): CookieInput => ({\n ...(typeof isRegex === 'string' ? { isRegex: isRegex.toLowerCase() === 'true' } : {}),\n name: Name,\n description: Notes,\n trackingPurposes: splitCsvToList(Purpose),\n // TODO: https://transcend.height.app/T-26391\n // service: Service,\n // Apply the trackerStatus to all values in the CSV -> allows for customer to define tracker status\n // on a row by row basis if needed\n status: Status || trackerStatus,\n owners: Owners ? splitCsvToList(Owners) : undefined,\n teams: Teams ? splitCsvToList(Teams) : undefined,\n // all remaining options are attribute\n attributes: Object.entries(rest)\n // filter out native columns that are exported from the admin dashboard\n // but not custom attributes\n .filter(([key]) => !OMIT_COLUMNS.includes(key))\n .map(([key, value]) => ({\n key,\n values: splitCsvToList(value),\n })),\n }),\n );\n\n // Upload the cookies into Transcend dashboard\n const syncedCookies = await syncCookies(client, validatedCookieInputs, { logger });\n\n // Log errors\n if (!syncedCookies) {\n logger.error(\n colors.red('Encountered error(s) syncing cookies from CSV, see logs above for more info. '),\n );\n process.exit(1);\n }\n}\n"],"mappings":"mUAUA,MAAM,EAAe,CACnB,KACA,WACA,aACA,eACA,uCACA,sBACA,sBACA,cACA,2BACD,CAOD,eAAsB,EAAqB,CACzC,OACA,gBACA,OACA,eAAe,GAUC,CAEhB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAG9D,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CA6CrD,MAHuB,EAAY,EAzCnB,EAAQ,EAAM,EAGO,CAAC,KACxC,CACC,YAAa,EACb,QAGA,UACA,UACA,SACA,SACA,QACA,OACA,GAAG,MACe,CAClB,GAAI,OAAO,GAAY,SAAW,CAAE,QAAS,EAAQ,aAAa,GAAK,OAAQ,CAAG,EAAE,CACpF,KAAM,EACN,YAAa,EACb,iBAAkB,EAAe,EAAQ,CAKzC,OAAQ,GAAU,EAClB,OAAQ,EAAS,EAAe,EAAO,CAAG,IAAA,GAC1C,MAAO,EAAQ,EAAe,EAAM,CAAG,IAAA,GAEvC,WAAY,OAAO,QAAQ,EAAK,CAG7B,QAAQ,CAAC,KAAS,CAAC,EAAa,SAAS,EAAI,CAAC,CAC9C,KAAK,CAAC,EAAK,MAAY,CACtB,MACA,OAAQ,EAAe,EAAM,CAC9B,EAAE,CACN,EAIkE,CAAE,CAAE,SAAQ,CAAC,GAIhF,EAAO,MACL,EAAO,IAAI,gFAAgF,CAC5F,CACD,QAAQ,KAAK,EAAE"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-
|
|
2
|
-
//# sourceMappingURL=uploadDataFlowsFromCsv-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{I as n}from"./codecs-CwRYEdQK.mjs";import{t as r}from"./readCsv-ddmavI86.mjs";import i from"colors";import{buildTranscendGraphQLClient as a,syncDataFlows as o}from"@transcend-io/sdk";import{splitCsvToList as s}from"@transcend-io/utils";const c=[`ID`,`Activity`,`Encounters`,`Last Seen At`,`Has Native Do Not Sell/Share Support`,`IAB USP API Support`,`Service Description`,`Website URL`,`Categories of Recipients`];async function l({auth:l,trackerStatus:u,file:d,classifyService:f=!1,transcendUrl:p=e}){let m=a(p,l);t.info(i.magenta(`Reading "${d}" from disk`)),await o(m,r(d,n).map(({Type:e,Notes:t,Service:n,Purpose:r,Status:i,Owners:a,Teams:o,"Connections Made To":l,...d})=>({value:l,type:e,description:t,trackingPurposes:s(r),status:i||u,owners:a?s(a):void 0,teams:o?s(o):void 0,attributes:Object.entries(d).filter(([e])=>!c.includes(e)).map(([e,t])=>({key:e,values:s(t)}))})),{classifyService:f,logger:t})||(t.error(i.red(`Encountered error(s) syncing data flows from CSV, see logs above for more info. `)),process.exit(1))}export{l as t};
|
|
2
|
+
//# sourceMappingURL=uploadDataFlowsFromCsv-B61qZpKb.mjs.map
|
package/dist/{uploadDataFlowsFromCsv-B854o0O5.mjs.map → uploadDataFlowsFromCsv-B61qZpKb.mjs.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"uploadDataFlowsFromCsv-
|
|
1
|
+
{"version":3,"file":"uploadDataFlowsFromCsv-B61qZpKb.mjs","names":[],"sources":["../src/lib/consent-manager/uploadDataFlowsFromCsv.ts"],"sourcesContent":["import { ConsentTrackerStatus } from '@transcend-io/privacy-types';\nimport { buildTranscendGraphQLClient, syncDataFlows } from '@transcend-io/sdk';\nimport { splitCsvToList } from '@transcend-io/utils';\nimport colors from 'colors';\n\nimport { DataFlowInput, DataFlowCsvInput } from '../../codecs.js';\nimport { DEFAULT_TRANSCEND_API } from '../../constants.js';\nimport { logger } from '../../logger.js';\nimport { readCsv } from '../requests/readCsv.js';\n\nconst OMIT_COLUMNS = [\n 'ID',\n 'Activity',\n 'Encounters',\n 'Last Seen At',\n 'Has Native Do Not Sell/Share Support',\n 'IAB USP API Support',\n 'Service Description',\n 'Website URL',\n 'Categories of Recipients',\n];\n\n/**\n * Upload a set of data flows from CSV\n *\n * @param options - Options\n */\nexport async function uploadDataFlowsFromCsv({\n auth,\n trackerStatus,\n file,\n classifyService = false,\n transcendUrl = DEFAULT_TRANSCEND_API,\n}: {\n /** CSV file path */\n file: string;\n /** Transcend API key authentication */\n auth: string;\n /** Sombra API key authentication */\n trackerStatus: ConsentTrackerStatus;\n /** classify data flow service if missing */\n classifyService?: boolean;\n /** API URL for Transcend backend */\n transcendUrl?: string;\n}): Promise<void> {\n // Build a GraphQL client\n const client = buildTranscendGraphQLClient(transcendUrl, auth);\n\n // Read from CSV the set of data flow inputs\n logger.info(colors.magenta(`Reading \"${file}\" from disk`));\n const dataFlowInputs = readCsv(file, DataFlowCsvInput);\n\n // Convert these data flow inputs into a format that the other function can use\n const validatedDataFlowInputs = dataFlowInputs.map(\n ({\n Type,\n Notes,\n // TODO: https://transcend.height.app/T-26391 - export in CSV\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n Service,\n Purpose,\n Status,\n Owners,\n Teams,\n 'Connections Made To': value,\n ...rest\n }): DataFlowInput => ({\n value,\n type: Type,\n description: Notes,\n trackingPurposes: splitCsvToList(Purpose),\n // TODO: https://transcend.height.app/T-26391\n // service: Service,\n // Apply the trackerStatus to all values in the CSV -> allows for customer to define tracker status\n // on a row by row basis if needed\n status: Status || trackerStatus,\n owners: Owners ? splitCsvToList(Owners) : undefined,\n teams: Teams ? splitCsvToList(Teams) : undefined,\n // all remaining options are attribute\n attributes: Object.entries(rest)\n // filter out native columns that are exported from the admin dashboard\n // but not custom attributes\n .filter(([key]) => !OMIT_COLUMNS.includes(key))\n .map(([key, value]) => ({\n key,\n values: splitCsvToList(value),\n })),\n }),\n );\n\n // Upload the data flows into Transcend dashboard\n const syncedDataFlows = await syncDataFlows(client, validatedDataFlowInputs, {\n classifyService,\n logger,\n });\n\n // Log errors\n if (!syncedDataFlows) {\n logger.error(\n colors.red(\n 'Encountered error(s) syncing data flows from CSV, see logs above for more info. ',\n ),\n );\n process.exit(1);\n }\n}\n"],"mappings":"qUAUA,MAAM,EAAe,CACnB,KACA,WACA,aACA,eACA,uCACA,sBACA,sBACA,cACA,2BACD,CAOD,eAAsB,EAAuB,CAC3C,OACA,gBACA,OACA,kBAAkB,GAClB,eAAe,GAYC,CAEhB,IAAM,EAAS,EAA4B,EAAc,EAAK,CAG9D,EAAO,KAAK,EAAO,QAAQ,YAAY,EAAK,aAAa,CAAC,CAgDrD,MANyB,EAAc,EAzCrB,EAAQ,EAAM,EAGS,CAAC,KAC5C,CACC,OACA,QAGA,UACA,UACA,SACA,SACA,QACA,sBAAuB,EACvB,GAAG,MACiB,CACpB,QACA,KAAM,EACN,YAAa,EACb,iBAAkB,EAAe,EAAQ,CAKzC,OAAQ,GAAU,EAClB,OAAQ,EAAS,EAAe,EAAO,CAAG,IAAA,GAC1C,MAAO,EAAQ,EAAe,EAAM,CAAG,IAAA,GAEvC,WAAY,OAAO,QAAQ,EAAK,CAG7B,QAAQ,CAAC,KAAS,CAAC,EAAa,SAAS,EAAI,CAAC,CAC9C,KAAK,CAAC,EAAK,MAAY,CACtB,MACA,OAAQ,EAAe,EAAM,CAC9B,EAAE,CACN,EAIwE,CAAE,CAC3E,kBACA,SACD,CAAC,GAIA,EAAO,MACL,EAAO,IACL,mFACD,CACF,CACD,QAAQ,KAAK,EAAE"}
|
package/dist/{uploadPrivacyRequestsFromCsv-D-RTgJsx.mjs → uploadPrivacyRequestsFromCsv-DFKHnc0H.mjs}
RENAMED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a as e}from"./constants-B7bW_WN2.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{a as n,c as r,i,l as a,o,r as s,s as c,t as l}from"./constants-BSZc6Ifl.mjs";import{n as u,t as d}from"./fuzzyMatchColumns-jgLi9Qnf.mjs";import{n as f,t as p}from"./parseAttributesFromString-COM7aBEj.mjs";import{t as m}from"./readCsv-ddmavI86.mjs";import{r as h,t as g}from"./extractClientError-_9Bqmm0v.mjs";import{CompletedRequestStatus as _,IdentifierType as v,IsoCountryCode as y,IsoCountrySubdivisionCode as b,NORMALIZE_PHONE_NUMBER as x,RequestAction as S}from"@transcend-io/privacy-types";import{startCase as C,uniq as w}from"lodash-es";import{apply as T,getEntries as E,getValues as D,valuesOf as O}from"@transcend-io/type-utils";import{join as ee}from"node:path";import k from"colors";import{LOCALE_KEY as A}from"@transcend-io/internationalization";import*as j from"io-ts";import{DATA_SUBJECTS as M,INITIALIZER as N,buildTranscendGraphQLClient as te,createSombraGotInstance as ne,fetchAllRequestAttributeKeys as re,makeGraphQLRequest as P}from"@transcend-io/sdk";import{map as F,splitCsvToList as I}from"@transcend-io/utils";import L from"cli-progress";import R from"inquirer";import z from"inquirer-autocomplete-prompt";import{DateFromISOString as B}from"io-ts-types";import{PersistedState as V}from"@transcend-io/persisted-state";async function H(e,t,n){R.registerPrompt(`autocomplete`,z);let r=e.map(e=>e||`<blank>`).filter(e=>!n[e]);if(r.length===0)return n;let i=await R.prompt(r.map(e=>({name:e,message:`Map value of: ${e}`,type:`autocomplete`,default:t.find(t=>u(e,t)),source:(e,n)=>n?t.filter(e=>typeof e==`string`&&u(n,e)):t})));return{...n,...T(i,e=>typeof e==`string`?e:Object.values(e)[0])}}function U(e,t){return w(e.map(e=>e[t]||``).flat())}async function W(e){let n=w(e.map(e=>Object.keys(e)).flat()),r=e,i=!0;for(;i;){let{filterColumnName:e}=await R.prompt([{name:`filterColumnName`,message:`If you need to filter the list of requests to import, choose the column to filter on. Currently ${r.length} rows.`,type:`list`,default:n,choices:[a,...n]}]);if(i=a!==e,i){let t=U(r,e),{valuesToKeep:i}=await R.prompt([{name:`valuesToKeep`,message:`Keep rows matching this value`,type:`checkbox`,default:n,choices:t}]);r=r.filter(t=>i.includes(t[e]))}}return t.info(k.magenta(`Importing ${r.length} requests`)),r}async function G(e,t){let n=D(o).filter(e=>!t.getValue(`columnNames`,e)),i=n.length===0?{}:await R.prompt(n.map(t=>{let n=C(t.replace(`ColumnName`,``)),i=d(e,n,r[t],!!s[t]);return{name:t,message:`Choose the column that will be used to map in the field: ${n}`,type:`list`,default:i[0],choices:i}}));return await Promise.all(E(i).map(([e,n])=>t.setValue(n,`columnNames`,e))),i}async function K(e,n,{state:r,columnNameMap:i}){let o=e=>r.getValue(`columnNames`,e)||i[e],{internalSubjects:s}=await P(e,M,{logger:t});t.info(k.magenta(`Determining mapping of columns for request action`));let c=await H(U(n,o(`requestType`)),Object.values(S),r.getValue(`requestTypeToRequestAction`));await r.setValue(c,`requestTypeToRequestAction`),t.info(k.magenta(`Determining mapping of columns for subject`));let l=await H(U(n,o(`subjectType`)),s.map(({type:e})=>e),r.getValue(`subjectTypeToSubjectName`));await r.setValue(l,`subjectTypeToSubjectName`),t.info(k.magenta(`Determining mapping of columns for locale`));let u=await H(U(n,o(`locale`)),Object.values(A),r.getValue(`languageToLocale`));await r.setValue(u,`languageToLocale`),t.info(k.magenta(`Determining mapping of columns for request status`)),t.info(k.magenta(`Determining mapping of columns for request status`));let d=o(`requestStatus`),f=d===`[NONE]`?{}:await H(U(n,d),[...Object.values(_),a],r.getValue(`statusToRequestStatus`));await r.setValue(f,`statusToRequestStatus`),t.info(k.magenta(`Determining mapping of columns for country`));let p=o(`country`),m=p===`[NONE]`?{}:await H(U(n,p),[...Object.values(y),a],r.getValue(`regionToCountry`));await r.setValue(m,`regionToCountry`),t.info(k.magenta(`Determining mapping of columns for country sub division`));let h=o(`countrySubDivision`),g=h===`[NONE]`?{}:await H(U(n,h),[...Object.values(b),a],r.getValue(`regionToCountrySubDivision`));await r.setValue(g,`regionToCountrySubDivision`)}const q=j.record(j.string,j.array(j.intersection([j.type({value:j.string}),j.partial({name:j.string})]))),J=j.intersection([j.type({email:j.string,attestedExtraIdentifiers:q,coreIdentifier:j.string,requestType:O(S),subjectType:j.string}),j.partial({country:O(y),countrySubDivision:O(b),attributes:j.array(p),status:O(_),createdAt:B,dataSiloIds:j.array(j.string),locale:O(A)})]);function Y(e,t,n){if(t===v.Email)return e.toLowerCase();if(t===v.Phone){let t=e.replace(x,``).replace(/[()]/g,``).replace(/[–]/g,``).replace(/[:]/g,``).replace(/[]/g,``).replace(/[A-Za-z]/g,``);return t?t.startsWith(`+`)?t:`+${n}${t}`:``}return e}function X(e,t,{columnNameMap:n,identifierNameMap:r,attributeNameMap:i,requestAttributeKeys:o,defaultPhoneCountryCode:s=`1`}){let c=e=>t.getValue(`columnNames`,e)||n[e];return e.map(e=>{let n={};Object.entries(r).filter(([,e])=>e!==a).forEach(([t,r])=>{let i=Object.values(v).includes(t)?t:v.Custom,a=e[r];if(a){let e=Y(a,i,s);e&&(n[i]||(n[i]=[]),n[i].push({value:e,name:t}))}});let u=[];Object.entries(i).filter(([,e])=>e!==a).forEach(([t,n])=>{let r=e[n];if(r){let e=o.find(e=>e.name===t)?.type===`MULTI_SELECT`;u.push({values:e?I(r):r,key:t})}});let d=c(`requestType`),f=c(`subjectType`);return[e,{email:e[c(`email`)],attestedExtraIdentifiers:n,attributes:u,coreIdentifier:e[c(`coreIdentifier`)],requestType:d===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`requestTypeToRequestAction`,l):t.getValue(`requestTypeToRequestAction`,e[d]),subjectType:f===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`subjectTypeToSubjectName`,l):t.getValue(`subjectTypeToSubjectName`,e[f]),...c(`locale`)!==`[NONE]`&&e[c(`locale`)]?{locale:t.getValue(`languageToLocale`,e[c(`locale`)])}:{},...c(`country`)!==`[NONE]`&&e[c(`country`)]?{country:t.getValue(`regionToCountry`,e[c(`country`)])}:{},...c(`countrySubDivision`)!==`[NONE]`&&e[c(`countrySubDivision`)]?{countrySubDivision:t.getValue(`regionToCountrySubDivision`,e[c(`countrySubDivision`)])}:{},...c(`requestStatus`)!==`[NONE]`&&t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])!==`[NONE]`&&e[c(`requestStatus`)]?{status:t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])}:{},...c(`createdAt`)!==`[NONE]`&&e[c(`createdAt`)]?{createdAt:new Date(e[c(`createdAt`)])}:{},...c(`dataSiloIds`)!==`[NONE]`&&e[c(`dataSiloIds`)]?{dataSiloIds:I(e[c(`dataSiloIds`)])}:{}}]})}async function Z(e,n,r){let{initializer:i}=await P(e,N,{logger:t}),a=i.identifiers.filter(({name:e})=>!r.getValue(`identifierNames`,e)&&!c.includes(e)),o=a.length===0?{}:await R.prompt(a.map(({name:e})=>{let t=d(n,e,!1);return{name:e,message:`Choose the column that will be used to map in the identifier: ${e}`,type:`list`,default:t[0],choices:t}}));return await Promise.all(Object.entries(o).map(([e,t])=>r.setValue(t,`identifierNames`,e))),{...r.getValue(`identifierNames`),...o}}async function Q(e,t,n,r){let i=r.filter(({name:e})=>!n.getValue(`attributeNames`,e)),a=i.length===0?{}:await R.prompt(i.map(({name:e})=>{let n=d(t,e,!1);return{name:e,message:`Choose the column that will be used to map in the attribute: ${e}`,type:`list`,default:n[0],choices:n}}));return await Promise.all(Object.entries(a).map(([e,t])=>n.setValue(t,`attributeNames`,e))),{...n.getValue(`attributeNames`),...a}}async function $({cacheFilepath:r,requestReceiptFolder:a,file:o,auth:s,sombraAuth:c,concurrency:l=100,defaultPhoneCountryCode:u=`1`,transcendUrl:d=e,attributes:p=[],emailIsVerified:_=!0,skipFilterStep:v=!1,skipSendingReceipt:y=!0,isTest:b=!1,isSilent:x=!0,debug:S=!1,dryRun:C=!1}){let T=new Date().getTime(),E=new L.SingleBar({},L.Presets.shades_classic),D=f(p),O=new V(r,i,{columnNames:{},requestTypeToRequestAction:{},subjectTypeToSubjectName:{},languageToLocale:{},statusToRequestStatus:{},identifierNames:{},attributeNames:{},regionToCountrySubDivision:{},regionToCountry:{}}),A=ee(a,`tr-request-upload-${new Date().toISOString().replace(/:/g,`-`)}-${o.split(`/`).pop()}`.replace(`.csv`,`.json`)),M=new V(A,n,{successfulRequests:[],duplicateRequests:[],failingRequests:[]}),N=await ne(d,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),P=m(o,j.record(j.string,j.string)),I=w(P.map(e=>Object.keys(e)).flat());if(P.length===0)throw Error(`No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.`);if(S){let e=P[0];t.info(k.magenta(`First request: ${JSON.stringify(e,null,2)}`))}let R=v?P:await W(P),z=te(d,s),B=await re(z,{logger:t}),H=await G(I,O),U=await Z(z,I,O),q=await Q(z,I,O,B);await K(z,R,{state:O,columnNameMap:H});let J=X(R,O,{defaultPhoneCountryCode:u,columnNameMap:H,identifierNameMap:U,attributeNameMap:q,requestAttributeKeys:B});S||E.start(J.length,0);let Y=0;await F(J,async([e,n],r)=>{let i=S?`email:${n.email} | coreIdentifier:${n.coreIdentifier}`:`row:${r.toString()}`;if(S&&t.info(k.magenta(`[${r+1}/${J.length}] Importing: ${JSON.stringify(n,null,2)}`)),C){t.info(k.magenta(`Bailing out on dry run because dryRun is set`));return}try{let a=await h(N,n,{details:`Uploaded by Transcend Cli: "tr-request-upload" : ${JSON.stringify(e,null,2)}`,isTest:b,emailIsVerified:_,skipSendingReceipt:y,isSilent:x,additionalAttributes:D});S&&(t.info(k.green(`[${r+1}/${J.length}] Successfully submitted the test data subject request: "${i}"`)),t.info(k.green(`[${r+1}/${J.length}] View it at: "${a.link}"`)));let o=M.getValue(`successfulRequests`);o.push({id:a.id,link:a.link,rowIndex:r,coreIdentifier:a.coreIdentifier,attemptedAt:new Date().toISOString()}),await M.setValue(o,`successfulRequests`)}catch(e){let a=`${e.message} - ${JSON.stringify(e.response?.body,null,2)}`,o=g(a);if(o===`Client error: You have already made this request.`){S&&t.info(k.yellow(`[${r+1}/${J.length}] Skipping request as it is a duplicate`));let e=M.getValue(`duplicateRequests`);e.push({coreIdentifier:n.coreIdentifier,rowIndex:r,attemptedAt:new Date().toISOString()}),await M.setValue(e,`duplicateRequests`)}else{let e=M.getValue(`failingRequests`);e.push({...n,rowIndex:r,error:o||a,attemptedAt:new Date().toISOString()}),await M.setValue(e,`failingRequests`),S&&(t.error(k.red(o||a)),t.error(k.red(`[${r+1}/${J.length}] Failed to submit request for: "${i}"`)))}}Y+=1,S||E.update(Y)},{concurrency:l}),E.stop();let $=new Date().getTime()-T;t.info(k.green(`Completed upload in "${$/1e3}" seconds.`)),M.getValue(`duplicateRequests`).length>0&&t.info(k.yellow(`Encountered "${M.getValue(`duplicateRequests`).length}" duplicate requests. See "${A}" to review the core identifiers for these requests.`)),M.getValue(`failingRequests`).length>0&&(t.error(k.red(`Encountered "${M.getValue(`failingRequests`).length}" errors. See "${A}" to review the error messages and inputs.`)),process.exit(1))}export{J as a,K as c,U as d,H as f,q as i,G as l,Q as n,X as o,Z as r,Y as s,$ as t,W as u};
|
|
2
|
-
//# sourceMappingURL=uploadPrivacyRequestsFromCsv-
|
|
1
|
+
import{a as e}from"./constants-BeMH_MWn.mjs";import{t}from"./logger-D1tXVwJj.mjs";import{a as n,c as r,i,l as a,o,r as s,s as c,t as l}from"./constants-BSZc6Ifl.mjs";import{n as u,t as d}from"./fuzzyMatchColumns-jgLi9Qnf.mjs";import{n as f,t as p}from"./parseAttributesFromString-COM7aBEj.mjs";import{t as m}from"./readCsv-ddmavI86.mjs";import{r as h,t as g}from"./extractClientError-_9Bqmm0v.mjs";import{CompletedRequestStatus as _,IdentifierType as v,IsoCountryCode as y,IsoCountrySubdivisionCode as b,NORMALIZE_PHONE_NUMBER as x,RequestAction as S}from"@transcend-io/privacy-types";import{startCase as C,uniq as w}from"lodash-es";import{apply as T,getEntries as E,getValues as D,valuesOf as O}from"@transcend-io/type-utils";import{join as ee}from"node:path";import k from"colors";import{LOCALE_KEY as A}from"@transcend-io/internationalization";import*as j from"io-ts";import{DATA_SUBJECTS as M,INITIALIZER as N,buildTranscendGraphQLClient as te,createSombraGotInstance as ne,fetchAllRequestAttributeKeys as re,makeGraphQLRequest as P}from"@transcend-io/sdk";import{map as F,splitCsvToList as I}from"@transcend-io/utils";import L from"cli-progress";import R from"inquirer";import z from"inquirer-autocomplete-prompt";import{DateFromISOString as B}from"io-ts-types";import{PersistedState as V}from"@transcend-io/persisted-state";async function H(e,t,n){R.registerPrompt(`autocomplete`,z);let r=e.map(e=>e||`<blank>`).filter(e=>!n[e]);if(r.length===0)return n;let i=await R.prompt(r.map(e=>({name:e,message:`Map value of: ${e}`,type:`autocomplete`,default:t.find(t=>u(e,t)),source:(e,n)=>n?t.filter(e=>typeof e==`string`&&u(n,e)):t})));return{...n,...T(i,e=>typeof e==`string`?e:Object.values(e)[0])}}function U(e,t){return w(e.map(e=>e[t]||``).flat())}async function W(e){let n=w(e.map(e=>Object.keys(e)).flat()),r=e,i=!0;for(;i;){let{filterColumnName:e}=await R.prompt([{name:`filterColumnName`,message:`If you need to filter the list of requests to import, choose the column to filter on. Currently ${r.length} rows.`,type:`list`,default:n,choices:[a,...n]}]);if(i=a!==e,i){let t=U(r,e),{valuesToKeep:i}=await R.prompt([{name:`valuesToKeep`,message:`Keep rows matching this value`,type:`checkbox`,default:n,choices:t}]);r=r.filter(t=>i.includes(t[e]))}}return t.info(k.magenta(`Importing ${r.length} requests`)),r}async function G(e,t){let n=D(o).filter(e=>!t.getValue(`columnNames`,e)),i=n.length===0?{}:await R.prompt(n.map(t=>{let n=C(t.replace(`ColumnName`,``)),i=d(e,n,r[t],!!s[t]);return{name:t,message:`Choose the column that will be used to map in the field: ${n}`,type:`list`,default:i[0],choices:i}}));return await Promise.all(E(i).map(([e,n])=>t.setValue(n,`columnNames`,e))),i}async function K(e,n,{state:r,columnNameMap:i}){let o=e=>r.getValue(`columnNames`,e)||i[e],{internalSubjects:s}=await P(e,M,{logger:t});t.info(k.magenta(`Determining mapping of columns for request action`));let c=await H(U(n,o(`requestType`)),Object.values(S),r.getValue(`requestTypeToRequestAction`));await r.setValue(c,`requestTypeToRequestAction`),t.info(k.magenta(`Determining mapping of columns for subject`));let l=await H(U(n,o(`subjectType`)),s.map(({type:e})=>e),r.getValue(`subjectTypeToSubjectName`));await r.setValue(l,`subjectTypeToSubjectName`),t.info(k.magenta(`Determining mapping of columns for locale`));let u=await H(U(n,o(`locale`)),Object.values(A),r.getValue(`languageToLocale`));await r.setValue(u,`languageToLocale`),t.info(k.magenta(`Determining mapping of columns for request status`)),t.info(k.magenta(`Determining mapping of columns for request status`));let d=o(`requestStatus`),f=d===`[NONE]`?{}:await H(U(n,d),[...Object.values(_),a],r.getValue(`statusToRequestStatus`));await r.setValue(f,`statusToRequestStatus`),t.info(k.magenta(`Determining mapping of columns for country`));let p=o(`country`),m=p===`[NONE]`?{}:await H(U(n,p),[...Object.values(y),a],r.getValue(`regionToCountry`));await r.setValue(m,`regionToCountry`),t.info(k.magenta(`Determining mapping of columns for country sub division`));let h=o(`countrySubDivision`),g=h===`[NONE]`?{}:await H(U(n,h),[...Object.values(b),a],r.getValue(`regionToCountrySubDivision`));await r.setValue(g,`regionToCountrySubDivision`)}const q=j.record(j.string,j.array(j.intersection([j.type({value:j.string}),j.partial({name:j.string})]))),J=j.intersection([j.type({email:j.string,attestedExtraIdentifiers:q,coreIdentifier:j.string,requestType:O(S),subjectType:j.string}),j.partial({country:O(y),countrySubDivision:O(b),attributes:j.array(p),status:O(_),createdAt:B,dataSiloIds:j.array(j.string),locale:O(A)})]);function Y(e,t,n){if(t===v.Email)return e.toLowerCase();if(t===v.Phone){let t=e.replace(x,``).replace(/[()]/g,``).replace(/[–]/g,``).replace(/[:]/g,``).replace(/[]/g,``).replace(/[A-Za-z]/g,``);return t?t.startsWith(`+`)?t:`+${n}${t}`:``}return e}function X(e,t,{columnNameMap:n,identifierNameMap:r,attributeNameMap:i,requestAttributeKeys:o,defaultPhoneCountryCode:s=`1`}){let c=e=>t.getValue(`columnNames`,e)||n[e];return e.map(e=>{let n={};Object.entries(r).filter(([,e])=>e!==a).forEach(([t,r])=>{let i=Object.values(v).includes(t)?t:v.Custom,a=e[r];if(a){let e=Y(a,i,s);e&&(n[i]||(n[i]=[]),n[i].push({value:e,name:t}))}});let u=[];Object.entries(i).filter(([,e])=>e!==a).forEach(([t,n])=>{let r=e[n];if(r){let e=o.find(e=>e.name===t)?.type===`MULTI_SELECT`;u.push({values:e?I(r):r,key:t})}});let d=c(`requestType`),f=c(`subjectType`);return[e,{email:e[c(`email`)],attestedExtraIdentifiers:n,attributes:u,coreIdentifier:e[c(`coreIdentifier`)],requestType:d===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`requestTypeToRequestAction`,l):t.getValue(`requestTypeToRequestAction`,e[d]),subjectType:f===`[APPLY VALUE TO ALL ROWS]`?t.getValue(`subjectTypeToSubjectName`,l):t.getValue(`subjectTypeToSubjectName`,e[f]),...c(`locale`)!==`[NONE]`&&e[c(`locale`)]?{locale:t.getValue(`languageToLocale`,e[c(`locale`)])}:{},...c(`country`)!==`[NONE]`&&e[c(`country`)]?{country:t.getValue(`regionToCountry`,e[c(`country`)])}:{},...c(`countrySubDivision`)!==`[NONE]`&&e[c(`countrySubDivision`)]?{countrySubDivision:t.getValue(`regionToCountrySubDivision`,e[c(`countrySubDivision`)])}:{},...c(`requestStatus`)!==`[NONE]`&&t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])!==`[NONE]`&&e[c(`requestStatus`)]?{status:t.getValue(`statusToRequestStatus`,e[c(`requestStatus`)])}:{},...c(`createdAt`)!==`[NONE]`&&e[c(`createdAt`)]?{createdAt:new Date(e[c(`createdAt`)])}:{},...c(`dataSiloIds`)!==`[NONE]`&&e[c(`dataSiloIds`)]?{dataSiloIds:I(e[c(`dataSiloIds`)])}:{}}]})}async function Z(e,n,r){let{initializer:i}=await P(e,N,{logger:t}),a=i.identifiers.filter(({name:e})=>!r.getValue(`identifierNames`,e)&&!c.includes(e)),o=a.length===0?{}:await R.prompt(a.map(({name:e})=>{let t=d(n,e,!1);return{name:e,message:`Choose the column that will be used to map in the identifier: ${e}`,type:`list`,default:t[0],choices:t}}));return await Promise.all(Object.entries(o).map(([e,t])=>r.setValue(t,`identifierNames`,e))),{...r.getValue(`identifierNames`),...o}}async function Q(e,t,n,r){let i=r.filter(({name:e})=>!n.getValue(`attributeNames`,e)),a=i.length===0?{}:await R.prompt(i.map(({name:e})=>{let n=d(t,e,!1);return{name:e,message:`Choose the column that will be used to map in the attribute: ${e}`,type:`list`,default:n[0],choices:n}}));return await Promise.all(Object.entries(a).map(([e,t])=>n.setValue(t,`attributeNames`,e))),{...n.getValue(`attributeNames`),...a}}async function $({cacheFilepath:r,requestReceiptFolder:a,file:o,auth:s,sombraAuth:c,concurrency:l=100,defaultPhoneCountryCode:u=`1`,transcendUrl:d=e,attributes:p=[],emailIsVerified:_=!0,skipFilterStep:v=!1,skipSendingReceipt:y=!0,isTest:b=!1,isSilent:x=!0,debug:S=!1,dryRun:C=!1}){let T=new Date().getTime(),E=new L.SingleBar({},L.Presets.shades_classic),D=f(p),O=new V(r,i,{columnNames:{},requestTypeToRequestAction:{},subjectTypeToSubjectName:{},languageToLocale:{},statusToRequestStatus:{},identifierNames:{},attributeNames:{},regionToCountrySubDivision:{},regionToCountry:{}}),A=ee(a,`tr-request-upload-${new Date().toISOString().replace(/:/g,`-`)}-${o.split(`/`).pop()}`.replace(`.csv`,`.json`)),M=new V(A,n,{successfulRequests:[],duplicateRequests:[],failingRequests:[]}),N=await ne(d,s,{logger:t,sombraApiKey:c,sombraUrl:process.env.SOMBRA_URL}),P=m(o,j.record(j.string,j.string)),I=w(P.map(e=>Object.keys(e)).flat());if(P.length===0)throw Error(`No Requests found in list! Ensure the first row of the CSV is a header and the rest are requests.`);if(S){let e=P[0];t.info(k.magenta(`First request: ${JSON.stringify(e,null,2)}`))}let R=v?P:await W(P),z=te(d,s),B=await re(z,{logger:t}),H=await G(I,O),U=await Z(z,I,O),q=await Q(z,I,O,B);await K(z,R,{state:O,columnNameMap:H});let J=X(R,O,{defaultPhoneCountryCode:u,columnNameMap:H,identifierNameMap:U,attributeNameMap:q,requestAttributeKeys:B});S||E.start(J.length,0);let Y=0;await F(J,async([e,n],r)=>{let i=S?`email:${n.email} | coreIdentifier:${n.coreIdentifier}`:`row:${r.toString()}`;if(S&&t.info(k.magenta(`[${r+1}/${J.length}] Importing: ${JSON.stringify(n,null,2)}`)),C){t.info(k.magenta(`Bailing out on dry run because dryRun is set`));return}try{let a=await h(N,n,{details:`Uploaded by Transcend Cli: "tr-request-upload" : ${JSON.stringify(e,null,2)}`,isTest:b,emailIsVerified:_,skipSendingReceipt:y,isSilent:x,additionalAttributes:D});S&&(t.info(k.green(`[${r+1}/${J.length}] Successfully submitted the test data subject request: "${i}"`)),t.info(k.green(`[${r+1}/${J.length}] View it at: "${a.link}"`)));let o=M.getValue(`successfulRequests`);o.push({id:a.id,link:a.link,rowIndex:r,coreIdentifier:a.coreIdentifier,attemptedAt:new Date().toISOString()}),await M.setValue(o,`successfulRequests`)}catch(e){let a=`${e.message} - ${JSON.stringify(e.response?.body,null,2)}`,o=g(a);if(o===`Client error: You have already made this request.`){S&&t.info(k.yellow(`[${r+1}/${J.length}] Skipping request as it is a duplicate`));let e=M.getValue(`duplicateRequests`);e.push({coreIdentifier:n.coreIdentifier,rowIndex:r,attemptedAt:new Date().toISOString()}),await M.setValue(e,`duplicateRequests`)}else{let e=M.getValue(`failingRequests`);e.push({...n,rowIndex:r,error:o||a,attemptedAt:new Date().toISOString()}),await M.setValue(e,`failingRequests`),S&&(t.error(k.red(o||a)),t.error(k.red(`[${r+1}/${J.length}] Failed to submit request for: "${i}"`)))}}Y+=1,S||E.update(Y)},{concurrency:l}),E.stop();let $=new Date().getTime()-T;t.info(k.green(`Completed upload in "${$/1e3}" seconds.`)),M.getValue(`duplicateRequests`).length>0&&t.info(k.yellow(`Encountered "${M.getValue(`duplicateRequests`).length}" duplicate requests. See "${A}" to review the core identifiers for these requests.`)),M.getValue(`failingRequests`).length>0&&(t.error(k.red(`Encountered "${M.getValue(`failingRequests`).length}" errors. See "${A}" to review the error messages and inputs.`)),process.exit(1))}export{J as a,K as c,U as d,H as f,q as i,G as l,Q as n,X as o,Z as r,Y as s,$ as t,W as u};
|
|
2
|
+
//# sourceMappingURL=uploadPrivacyRequestsFromCsv-DFKHnc0H.mjs.map
|