@walkeros/server-destination-gcp 4.2.1-next-1781715165983 → 4.2.1
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/CHANGELOG.md +9 -3
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/walkerOS.json +5 -5
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
# @walkeros/server-destination-gcp
|
|
2
2
|
|
|
3
|
-
## 4.2.1
|
|
3
|
+
## 4.2.1
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
|
+
- 96c791a: The BigQuery destination now applies `config.credentials` to the
|
|
8
|
+
Storage Write client that performs event writes, not just the query client.
|
|
9
|
+
Event writes from the configured service account now succeed on non-Google
|
|
10
|
+
Cloud runtimes instead of failing with a credentials error. Both clients
|
|
11
|
+
resolve credentials the same way, so a destination always authenticates as a
|
|
12
|
+
single identity.
|
|
7
13
|
- 8afb7cc: Capture BigQuery Storage Write stream errors so a broken writer
|
|
8
14
|
routes events to the dead-letter queue instead of crashing the process, and
|
|
9
15
|
re-open a broken writer automatically on the next event.
|
|
@@ -12,8 +18,8 @@
|
|
|
12
18
|
- Updated dependencies [d1b41ca]
|
|
13
19
|
- Updated dependencies [0a8a08b]
|
|
14
20
|
- Updated dependencies [8afb7cc]
|
|
15
|
-
- @walkeros/core@4.2.1
|
|
16
|
-
- @walkeros/server-core@4.2.1
|
|
21
|
+
- @walkeros/core@4.2.1
|
|
22
|
+
- @walkeros/server-core@4.2.1
|
|
17
23
|
|
|
18
24
|
## 4.2.0
|
|
19
25
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _google_cloud_bigquery_storage from '@google-cloud/bigquery-storage';
|
|
2
2
|
import { managedwriter } from '@google-cloud/bigquery-storage';
|
|
3
3
|
import { DestinationServer } from '@walkeros/server-core';
|
|
4
|
-
import { Destination as Destination$2, Credential,
|
|
4
|
+
import { ServiceAccount, Destination as Destination$2, Credential, Mapping as Mapping$2, SetupFn as SetupFn$2 } from '@walkeros/core';
|
|
5
5
|
import { BigQuery, BigQueryOptions } from '@google-cloud/bigquery';
|
|
6
6
|
import { PubSub, TopicMetadata } from '@google-cloud/pubsub';
|
|
7
7
|
|
|
@@ -21,6 +21,14 @@ interface Settings$1 {
|
|
|
21
21
|
tableId: string;
|
|
22
22
|
location?: string;
|
|
23
23
|
bigquery?: BigQueryOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Service-account credentials parsed from `config.credentials` at init.
|
|
26
|
+
* Threaded to BOTH the query client (`new BigQuery`) and the data-plane
|
|
27
|
+
* Storage Write client (`new WriterClient`) so event writes authenticate
|
|
28
|
+
* with the configured SA instead of falling back to ADC on non-GCP runtimes.
|
|
29
|
+
* Runtime-only (the raw `config.credentials` may be a JSON string).
|
|
30
|
+
*/
|
|
31
|
+
credentials?: ServiceAccount;
|
|
24
32
|
writeClient?: managedwriter.WriterClient;
|
|
25
33
|
writer?: managedwriter.JSONWriter;
|
|
26
34
|
connection?: StreamConnection;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _google_cloud_bigquery_storage from '@google-cloud/bigquery-storage';
|
|
2
2
|
import { managedwriter } from '@google-cloud/bigquery-storage';
|
|
3
3
|
import { DestinationServer } from '@walkeros/server-core';
|
|
4
|
-
import { Destination as Destination$2, Credential,
|
|
4
|
+
import { ServiceAccount, Destination as Destination$2, Credential, Mapping as Mapping$2, SetupFn as SetupFn$2 } from '@walkeros/core';
|
|
5
5
|
import { BigQuery, BigQueryOptions } from '@google-cloud/bigquery';
|
|
6
6
|
import { PubSub, TopicMetadata } from '@google-cloud/pubsub';
|
|
7
7
|
|
|
@@ -21,6 +21,14 @@ interface Settings$1 {
|
|
|
21
21
|
tableId: string;
|
|
22
22
|
location?: string;
|
|
23
23
|
bigquery?: BigQueryOptions;
|
|
24
|
+
/**
|
|
25
|
+
* Service-account credentials parsed from `config.credentials` at init.
|
|
26
|
+
* Threaded to BOTH the query client (`new BigQuery`) and the data-plane
|
|
27
|
+
* Storage Write client (`new WriterClient`) so event writes authenticate
|
|
28
|
+
* with the configured SA instead of falling back to ADC on non-GCP runtimes.
|
|
29
|
+
* Runtime-only (the raw `config.credentials` may be a JSON string).
|
|
30
|
+
*/
|
|
31
|
+
credentials?: ServiceAccount;
|
|
24
32
|
writeClient?: managedwriter.WriterClient;
|
|
25
33
|
writer?: managedwriter.JSONWriter;
|
|
26
34
|
connection?: StreamConnection;
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationBigQuery:()=>types_exports,DestinationPubSub:()=>types_exports2,default:()=>destinationBigQuery,destinationBigQuery:()=>destinationBigQuery,destinationPubSub:()=>destinationPubSub}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_bigquery=require("@google-cloud/bigquery"),import_core=require("@walkeros/core"),import_pubsub=require("@google-cloud/pubsub");function resolveCredentials(partialConfig,logger){if(void 0!==partialConfig.credentials)return partialConfig.credentials;const fromSettings=partialConfig.settings?.credentials;return void 0!==fromSettings?(logger.warn("settings.credentials is deprecated; use config.credentials"),fromSettings):void 0}function parseCredentials(raw,logger){if(void 0!==raw){if("string"==typeof raw)try{const parsed=JSON.parse(raw);return function(value){if(!(0,import_core.isObject)(value))return!1;const candidate=value;return"string"==typeof candidate.client_email&&"string"==typeof candidate.private_key}(parsed)?parsed:void logger.throw("Invalid credentials JSON")}catch{return void logger.throw("Invalid credentials JSON")}return raw}}var import_core2=require("@walkeros/core");function jsonOrNull(value){return null==value?null:"object"==typeof value?Array.isArray(value)&&0===value.length?null:Array.isArray(value)||0!==Object.keys(value).length?JSON.stringify(value):null:JSON.stringify(value)}function eventToRow(event){return{name:event.name,data:jsonOrNull(event.data),context:jsonOrNull(event.context),globals:jsonOrNull(event.globals),custom:jsonOrNull(event.custom),user:jsonOrNull(event.user),nested:jsonOrNull(event.nested),consent:jsonOrNull(event.consent),id:event.id,trigger:event.trigger,entity:event.entity,action:event.action,timestamp:1e3*new Date(event.timestamp).getTime(),timing:event.timing,source:jsonOrNull(event.source)}}var import_bigquery_storage=require("@google-cloud/bigquery-storage");async function openWriter(args,logger){const{projectId:projectId,datasetId:datasetId,tableId:tableId,bigquery:bigquery,timeout:timeout,onConnectionError:onConnectionError}=args,destinationTable=`projects/${projectId}/datasets/${datasetId}/tables/${tableId}`;logger.debug("Opening BigQuery Storage Write API writer",{destinationTable:destinationTable});const callOptions=void 0===timeout?void 0:{timeout:timeout},writeClient=new import_bigquery_storage.managedwriter.WriterClient({projectId:projectId,...bigquery});let connectionErrorListener,connection;try{connection=await writeClient.createStreamConnection({destinationTable:destinationTable,streamId:import_bigquery_storage.managedwriter.DefaultStream},callOptions),onConnectionError&&(connectionErrorListener=connection.onConnectionError(err=>{onConnectionError(err)}));const streamId=connection.getStreamId(),writeStream=await writeClient.getWriteStream({streamId:streamId,view:import_bigquery_storage.protos.google.cloud.bigquery.storage.v1.WriteStreamView.FULL},callOptions);if(!writeStream.tableSchema)throw new Error(`BigQuery write stream ${streamId} returned no tableSchema; cannot build proto descriptor`);const protoDescriptor=import_bigquery_storage.adapt.convertStorageSchemaToProto2Descriptor(writeStream.tableSchema,"root");return{writeClient:writeClient,writer:new import_bigquery_storage.managedwriter.JSONWriter({connection:connection,protoDescriptor:protoDescriptor}),connection:connection,connectionErrorListener:connectionErrorListener}}catch(err){throw closeWriter({writeClient:writeClient,connectionErrorListener:connectionErrorListener},logger),err}}function ensureWriter(settings,logger){if(!settings.writerBroken)return Promise.resolve();if(settings.reopenInFlight)return settings.reopenInFlight;const lastError=settings.lastStreamError;logger.info("BigQuery writer broken; attempting one re-open before failing",{error:lastError?lastError.message:"unknown stream error"});const reopen=settings.reopenWriter;if(!reopen)return Promise.reject(new Error("BigQuery writer is broken and no re-open hook is configured: "+(lastError?lastError.message:"unknown stream error")));const inFlight=(async()=>{try{closeWriter({writer:settings.writer,writeClient:settings.writeClient,connectionErrorListener:settings.connectionErrorListener},logger);const handles=await reopen();settings.writeClient=handles.writeClient,settings.writer=handles.writer,settings.connection=handles.connection,settings.connectionErrorListener=handles.connectionErrorListener,settings.writerBroken=!1,settings.lastStreamError=void 0,logger.info("BigQuery writer re-opened after a stream error")}finally{settings.reopenInFlight=void 0}})();return settings.reopenInFlight=inFlight,inFlight}function closeWriter(handles,logger){try{handles.connectionErrorListener?.off()}catch(err){logger.warn("connection error listener removal failed",{error:String(err)})}try{handles.writer?.close()}catch(err){logger.warn("writer.close failed",{error:String(err)})}try{handles.writeClient?.close()}catch(err){logger.warn("writeClient.close failed",{error:String(err)})}}var import_core3=require("@walkeros/core"),import_core4=require("@walkeros/core"),DEFAULT_SETUP={location:"EU",storageBillingModel:"PHYSICAL",partitioning:{type:"DAY",field:"timestamp"},clustering:{fields:["name","entity","action"]},schema:[{name:"name",type:"STRING",mode:"REQUIRED"},{name:"data",type:"JSON",mode:"NULLABLE"},{name:"context",type:"JSON",mode:"NULLABLE"},{name:"globals",type:"JSON",mode:"NULLABLE"},{name:"custom",type:"JSON",mode:"NULLABLE"},{name:"user",type:"JSON",mode:"NULLABLE"},{name:"nested",type:"JSON",mode:"NULLABLE"},{name:"consent",type:"JSON",mode:"NULLABLE"},{name:"id",type:"STRING",mode:"NULLABLE"},{name:"trigger",type:"STRING",mode:"NULLABLE"},{name:"entity",type:"STRING",mode:"NULLABLE"},{name:"action",type:"STRING",mode:"NULLABLE"},{name:"timestamp",type:"TIMESTAMP",mode:"NULLABLE"},{name:"timing",type:"FLOAT64",mode:"NULLABLE"},{name:"source",type:"JSON",mode:"NULLABLE"}]};function isAlreadyExists(err){return function(err){return"object"==typeof err&&null!==err&&("code"in err&&"number"==typeof err.code)}(err)&&409===err.code}var types_exports={},destinationBigQuery={type:"gcp-bigquery",config:{},setup:async function(ctx){const{config:config,logger:logger}=ctx,merged=(0,import_core4.resolveSetup)(config.setup,DEFAULT_SETUP);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const options={location:merged.location??DEFAULT_SETUP.location,storageBillingModel:merged.storageBillingModel??DEFAULT_SETUP.storageBillingModel,partitioning:merged.partitioning??DEFAULT_SETUP.partitioning,clustering:merged.clustering??DEFAULT_SETUP.clustering,schema:merged.schema??DEFAULT_SETUP.schema};if(!config.settings)return void logger.throw("setup: settings missing");const{client:client,datasetId:datasetId,tableId:tableId}=config.settings;if(!client)return void logger.throw("setup: BigQuery client is missing");if(!datasetId)return void logger.throw("setup: datasetId is missing");if(!tableId)return void logger.throw("setup: tableId is missing");const dataset=client.dataset(datasetId);let datasetCreated=!1;const[datasetExists]=await dataset.exists();if(datasetExists)logger.debug("setup: dataset exists",{datasetId:datasetId});else try{await dataset.create({location:options.location,storageBillingModel:options.storageBillingModel}),datasetCreated=!0,logger.info("setup: dataset created",{datasetId:datasetId,location:options.location,storageBillingModel:options.storageBillingModel})}catch(err){if(!isAlreadyExists(err))throw err;logger.debug("setup: dataset already exists (race)",{datasetId:datasetId})}const table=dataset.table(tableId);let tableCreated=!1;const[tableExists]=await table.exists();if(tableExists)logger.debug("setup: table exists",{datasetId:datasetId,tableId:tableId}),await async function(table,options,logger){let metadata;try{const[meta]=await table.getMetadata();metadata=meta??{}}catch(err){return void logger.debug("setup: drift check failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}if(v=metadata.timePartitioning,"object"==typeof v&&null!==v){const actual=metadata.timePartitioning,declared=options.partitioning;actual.type===declared.type&&actual.field===declared.field||logger.warn("setup.drift",{field:"timePartitioning",declared:declared,actual:actual})}var v;if(function(v){return"object"==typeof v&&null!==v}(metadata.clustering)){const actualFields=metadata.clustering.fields??[],declaredFields=options.clustering.fields;(actualFields.length!==declaredFields.length||actualFields.some((f,i)=>f!==declaredFields[i]))&&logger.warn("setup.drift",{field:"clustering",declared:{fields:declaredFields},actual:{fields:actualFields}})}if(function(v){return"object"==typeof v&&null!==v}(metadata.schema)){const actualFields=metadata.schema.fields??[],declaredFields=options.schema;(actualFields.length!==declaredFields.length||declaredFields.some((d,i)=>{const a=actualFields[i];return!a||a.name!==d.name||a.type!==d.type||a.mode!==d.mode}))&&logger.warn("setup.drift",{field:"schema",declared:declaredFields.map(f=>({name:f.name,type:f.type,mode:f.mode})),actual:actualFields.map(f=>({name:f.name,type:f.type,mode:f.mode}))})}}(table,options,logger);else try{await table.create({schema:{fields:options.schema},timePartitioning:{type:options.partitioning.type,field:options.partitioning.field},clustering:{fields:options.clustering.fields}}),tableCreated=!0,logger.info("setup: table created",{datasetId:datasetId,tableId:tableId,partitioning:options.partitioning,clustering:options.clustering})}catch(err){if(!isAlreadyExists(err))throw err;logger.debug("setup: table already exists (race)",{tableId:tableId})}return{datasetCreated:datasetCreated,tableCreated:tableCreated}},async init({config:partialConfig,env:env,logger:logger,id:id,reportError:reportError}){const config=function(partialConfig={},env,logger){const settings=partialConfig.settings||{},{projectId:projectId,bigquery:bigquery}=settings;let{client:client,location:location,datasetId:datasetId,tableId:tableId}=settings;projectId||logger.throw("Config settings projectId missing"),location=location||"EU",datasetId=datasetId||"walkerOS",tableId=tableId||"events";const options={...bigquery||{}};options.projectId=projectId;const credentials=parseCredentials(partialConfig.credentials,logger);void 0!==credentials&&"string"!=typeof credentials&&(options.credentials=credentials);const BigQueryClass=env?.BigQuery||import_bigquery.BigQuery;client=client||new BigQueryClass(options);const settingsConfig={...settings,client:client,projectId:projectId,location:location,datasetId:datasetId,tableId:tableId};return{...partialConfig,settings:settingsConfig}}(partialConfig,env,logger),timeout=config.timeout&&config.timeout>0?config.timeout:1e4,{settings:settings}=config,onConnectionError=err=>{settings.writerBroken=!0,settings.lastStreamError=err instanceof Error?err:new Error(String(err)),reportError?.(err)};settings.reopenWriter=()=>openWriter({projectId:settings.projectId,datasetId:settings.datasetId,tableId:settings.tableId,bigquery:settings.bigquery,timeout:timeout,onConnectionError:onConnectionError},logger);try{const{writer:writer,writeClient:writeClient,connection:connection,connectionErrorListener:connectionErrorListener}=await openWriter({projectId:settings.projectId,datasetId:settings.datasetId,tableId:settings.tableId,bigquery:settings.bigquery,timeout:timeout,onConnectionError:onConnectionError},logger);settings.writer=writer,settings.writeClient=writeClient,settings.connection=connection,settings.connectionErrorListener=connectionErrorListener}catch(err){const message=err instanceof Error?err.message:String(err);if(function(err){if("object"!=typeof err||null===err)return!1;if(!("code"in err))return!1;const obj=err;return 5===obj.code||404===obj.code}(err)){const target=`${config.settings.datasetId}.${config.settings.tableId}`,project=config.settings.projectId;logger.error(`BigQuery dataset or table not found: project="${project}", target="${target}". Run "walkeros setup destination.${id}" to create them.`,{project:project,target:target,error:message})}else logger.error("BigQuery init failed",{error:message});throw err}return config},push:async(event,context)=>await async function(event,{config:config,rule:_rule,data:data,logger:logger}){const settings=config.settings;if(!settings)return logger.throw("settings missing, init() not run");if(!settings.writer)return logger.throw("writer is missing, init() not run");settings.writerBroken&&await ensureWriter(settings,logger);const{writer:writer,datasetId:datasetId,tableId:tableId}=settings;if(!writer)return logger.throw("writer is missing, init() not run");if(!datasetId)return logger.throw("datasetId is missing");if(!tableId)return logger.throw("tableId is missing");const rows=[(0,import_core2.isObject)(data)?data:eventToRow(event)];let result;logger.debug("Calling BigQuery Storage Write API",{dataset:datasetId,table:tableId,rowCount:rows.length});try{const pending=writer.appendRows(rows);result=await pending.getResult()}catch(err){throw logger.error("BigQuery row append threw",{error:err instanceof Error?err.message:String(err)}),err}if(result.rowErrors&&result.rowErrors.length>0){const first=result.rowErrors[0];return logger.throw(`BigQuery row append failed: code=${first.code} message=${first.message}`)}logger.debug("BigQuery Storage Write API response",{ok:!0,offset:result.appendResult?.offset?.value})}(event,context),pushBatch:async(batch,{config:config,logger:logger})=>{const settings=config.settings;if(!settings)return logger.throw("settings missing, init() not run");if(!settings.writer)return logger.throw("writer is missing, init() not run");settings.writerBroken&&await ensureWriter(settings,logger);const{writer:writer,datasetId:datasetId,tableId:tableId}=settings;if(!writer)return logger.throw("writer is missing, init() not run");if(!datasetId)return logger.throw("datasetId is missing");if(!tableId)return logger.throw("tableId is missing");const rows=batch.entries.map(e=>(0,import_core3.isObject)(e.data)?e.data:eventToRow(e.event));if(0!==rows.length)try{logger.debug("Calling BigQuery Storage Write API (batch)",{dataset:datasetId,table:tableId,rowCount:rows.length});const pending=writer.appendRows(rows),result=await pending.getResult();if(result.rowErrors&&result.rowErrors.length>0){const failed=result.rowErrors.length;logger.info("BigQuery batch had partial failures",{ok:rows.length-failed,failed:failed,rowCount:rows.length});for(const re of result.rowErrors)logger.error("BigQuery row append failed",{index:re.index,code:re.code,message:re.message});return{failed:result.rowErrors.map(re=>({index:Number(re.index),error:Object.assign(new Error(re.message??"BigQuery row error"),{code:re.code})}))}}logger.debug("BigQuery batch append ok",{ok:rows.length,failed:0,offset:result.appendResult?.offset?.value})}catch(err){throw logger.error("BigQuery batch append threw",{error:err instanceof Error?err.message:String(err)}),err}},async destroy({config:config,logger:logger}){config.settings&&closeWriter({writer:config.settings.writer,writeClient:config.settings.writeClient,connectionErrorListener:config.settings.connectionErrorListener},logger)}};var import_core5=require("@walkeros/core");function hasNumericCode(err){if("object"!=typeof err||null===err)return!1;if(!("code"in err))return!1;return"number"==typeof err.code}var push2=async function(event,{config:config,rule:rule,data:data,collector:collector,logger:logger,id:id}){const settings=config.settings;if(!function(value){if("object"!=typeof value||null===value)return!1;const candidate=value;return void 0!==candidate.client&&"string"==typeof candidate.projectId&&candidate.projectId.length>0&&"string"==typeof candidate.topic&&candidate.topic.length>0}(settings))return logger.throw("settings missing or incomplete, init() not run");const{client:client,projectId:projectId,topic:defaultTopic}=settings,ruleSettings=(value=rule?.settings,"object"==typeof value&&null!==value?rule.settings:{});var value;const topicName="string"==typeof ruleSettings.topic&&ruleSettings.topic.length>0?ruleSettings.topic:defaultTopic,orderingValue=void 0!==ruleSettings.orderingKey?ruleSettings.orderingKey:settings.orderingKey;let orderingKey;if(void 0!==orderingValue){const resolved=await(0,import_core5.getMappingValue)(event,orderingValue,{collector:collector});"string"==typeof resolved&&resolved.length>0?orderingKey=resolved:null!=resolved&&(orderingKey=String(resolved))}const topicHandle=client.topic(topicName,{messageOrdering:Boolean(orderingKey)}),body=(0,import_core5.isObject)(data)&&Object.keys(data).length>0?data:event,payload=Buffer.from(JSON.stringify(body)),baseAttrs=settings.attributes,ruleAttrs=ruleSettings.attributes,attributes=await async function(event,base,override,collector){const result={};let touched=!1;if(base)for(const[key,value]of Object.entries(base)){const resolved=await(0,import_core5.getMappingValue)(event,value,{collector:collector});"string"==typeof resolved?(result[key]=resolved,touched=!0):null!=resolved&&(result[key]=String(resolved),touched=!0)}if(override)for(const[key,value]of Object.entries(override)){const resolved=await(0,import_core5.getMappingValue)(event,value,{collector:collector});"string"==typeof resolved?(result[key]=resolved,touched=!0):null!=resolved&&(result[key]=String(resolved),touched=!0)}return touched?result:void 0}(event,baseAttrs,ruleAttrs,collector),message={data:payload};attributes&&function(value){if(!(0,import_core5.isObject)(value))return!1;const candidate=value;for(const key of Object.keys(candidate))if("string"!=typeof candidate[key])return!1;return!0}(attributes)&&(message.attributes=attributes),orderingKey&&(message.orderingKey=orderingKey),logger.debug("Pub/Sub publish",{topic:topicName,projectId:projectId,orderingKey:orderingKey,event:event.name});try{await topicHandle.publishMessage(message)}catch(err){if(orderingKey)try{topicHandle.resumePublishing(orderingKey)}catch(resumeErr){logger.debug("Pub/Sub resumePublishing failed",{orderingKey:orderingKey,error:resumeErr instanceof Error?resumeErr.message:String(resumeErr)})}throw!function(err){return hasNumericCode(err)&&(5===err.code||404===err.code)}(err)?!function(err){return hasNumericCode(err)&&(7===err.code||16===err.code||403===err.code||401===err.code)}(err)?logger.error("Pub/Sub publish failed",{topic:topicName,projectId:projectId,event:event.name,error:err instanceof Error?err.message:String(err)}):logger.error(`Pub/Sub publish denied for topic "${topicName}". Grant the runtime service account roles/pubsub.publisher on this topic.`,{topic:topicName,projectId:projectId,error:err instanceof Error?err.message:String(err)}):logger.error(`Pub/Sub topic "${topicName}" not found in project "${projectId}". Run "walkeros setup destination.${id}" to create it.`,{topic:topicName,projectId:projectId,error:err instanceof Error?err.message:String(err)}),err}},import_core6=require("@walkeros/core"),import_pubsub2=require("@google-cloud/pubsub"),DEFAULT_SETUP2={messageStoragePolicy:{allowedPersistenceRegions:["europe-west1","europe-west3","europe-west4"]}};async function safeClose(client,logger){try{await client.close()}catch(err){logger.debug("setup: client.close failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}}var types_exports2={},destinationPubSub={type:"gcp-pubsub",config:{},setup:async function(ctx){const{config:config,env:env,logger:logger}=ctx,merged=(0,import_core6.resolveSetup)(config.setup,DEFAULT_SETUP2);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const settings=config.settings;if(!settings)return void logger.throw("setup: settings missing");const projectId="string"==typeof settings.projectId?settings.projectId:"",topicName="string"==typeof settings.topic?settings.topic:"";if(!projectId)return void logger.throw("setup: projectId is missing");if(!topicName)return void logger.throw("setup: topic is missing");let createdClient=!1,client=settings.client;if(!client){const Constructor=env?.PubSub??import_pubsub2.PubSub,credentials=parseCredentials(resolveCredentials(config,logger),logger);client=new Constructor({projectId:projectId,...void 0!==credentials&&"string"!=typeof credentials?{credentials:credentials}:{},...settings.apiEndpoint?{apiEndpoint:settings.apiEndpoint}:{}}),createdClient=!0}const topic=client.topic(topicName);let topicCreated=!1;const[exists]=await topic.exists();if(exists)logger.debug("setup: topic exists",{topic:topicName}),await async function(topic,declared,logger){let metadata;try{const[meta]=await topic.getMetadata();metadata=meta??{}}catch(err){return void logger.debug("setup: drift check failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}if(declared.messageStoragePolicy){const declaredRegions=declared.messageStoragePolicy.allowedPersistenceRegions,actualRegions=metadata.messageStoragePolicy?.allowedPersistenceRegions??[];(declaredRegions.length!==actualRegions.length||declaredRegions.some((r,i)=>r!==actualRegions[i]))&&logger.warn("setup.drift",{field:"messageStoragePolicy.allowedPersistenceRegions",declared:declaredRegions,actual:actualRegions})}if(declared.messageRetentionDuration){const declaredSeconds=declared.messageRetentionDuration.seconds,actualRaw=metadata.messageRetentionDuration?.seconds,actualSeconds="number"==typeof actualRaw?actualRaw:"string"==typeof actualRaw?Number(actualRaw):void 0;actualSeconds!==declaredSeconds&&logger.warn("setup.drift",{field:"messageRetentionDuration.seconds",declared:declaredSeconds,actual:actualSeconds??null})}void 0!==declared.kmsKeyName&&metadata.kmsKeyName!==declared.kmsKeyName&&logger.warn("setup.drift",{field:"kmsKeyName",declared:declared.kmsKeyName,actual:metadata.kmsKeyName??null});if(declared.labels){const actualLabels=metadata.labels??{};for(const[key,declaredValue]of Object.entries(declared.labels)){const actualValue=actualLabels[key];actualValue!==declaredValue&&logger.warn("setup.drift",{field:`labels.${key}`,declared:declaredValue,actual:actualValue??null})}}}(topic,merged,logger);else{const metadata={name:`projects/${projectId}/topics/${topicName}`};merged.messageStoragePolicy&&(metadata.messageStoragePolicy=merged.messageStoragePolicy),merged.messageRetentionDuration&&(metadata.messageRetentionDuration={seconds:merged.messageRetentionDuration.seconds}),merged.kmsKeyName&&(metadata.kmsKeyName=merged.kmsKeyName),merged.labels&&(metadata.labels=merged.labels);try{await client.createTopic(metadata),topicCreated=!0,logger.info("setup: topic created",{topic:topicName,projectId:projectId,messageStoragePolicy:metadata.messageStoragePolicy})}catch(err){if(!function(err){return function(err){return"object"==typeof err&&null!==err&&"code"in err&&"number"==typeof err.code}(err)&&(6===err.code||409===err.code)}(err))throw createdClient&&await safeClose(client,logger),err;logger.debug("setup: topic already exists (race)",{topic:topicName})}}return createdClient&&await safeClose(client,logger),{topicCreated:topicCreated}},init:async({config:partialConfig,env:env,logger:logger})=>function(partialConfig={},env,logger){const settings=partialConfig.settings??{},projectId="string"==typeof settings.projectId?settings.projectId:"",topic="string"==typeof settings.topic?settings.topic:"";projectId||logger.throw("Config settings projectId missing"),topic||logger.throw("Config settings topic missing");const credentials=parseCredentials(resolveCredentials(partialConfig,logger),logger);let client=settings.client;client||(client=new(env?.PubSub??import_pubsub.PubSub)(function(input){const result={projectId:input.projectId};return void 0!==input.credentials&&"string"!=typeof input.credentials&&(result.credentials=input.credentials),input.apiEndpoint&&(result.apiEndpoint=input.apiEndpoint),result}({projectId:projectId,credentials:credentials,apiEndpoint:settings.apiEndpoint})));const settingsConfig={...settings,client:client,projectId:projectId,topic:topic,credentials:credentials};return{...partialConfig,settings:settingsConfig}}(partialConfig,env,logger),push:async(event,context)=>await push2(event,context),async destroy({config:config}){if(!config.settings)return;const{client:client}=config.settings;if(client)try{await client.close()}catch{}}};//# sourceMappingURL=index.js.map
|
|
1
|
+
"use strict";var mod,__defProp=Object.defineProperty,__getOwnPropDesc=Object.getOwnPropertyDescriptor,__getOwnPropNames=Object.getOwnPropertyNames,__hasOwnProp=Object.prototype.hasOwnProperty,index_exports={};((target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})})(index_exports,{DestinationBigQuery:()=>types_exports,DestinationPubSub:()=>types_exports2,default:()=>destinationBigQuery,destinationBigQuery:()=>destinationBigQuery,destinationPubSub:()=>destinationPubSub}),module.exports=(mod=index_exports,((to,from,except,desc)=>{if(from&&"object"==typeof from||"function"==typeof from)for(let key of __getOwnPropNames(from))__hasOwnProp.call(to,key)||key===except||__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to})(__defProp({},"__esModule",{value:!0}),mod));var import_bigquery=require("@google-cloud/bigquery"),import_core=require("@walkeros/core"),import_pubsub=require("@google-cloud/pubsub");function resolveCredentials(partialConfig,logger){if(void 0!==partialConfig.credentials)return partialConfig.credentials;const fromSettings=partialConfig.settings?.credentials;return void 0!==fromSettings?(logger.warn("settings.credentials is deprecated; use config.credentials"),fromSettings):void 0}function parseCredentials(raw,logger){if(void 0!==raw){if("string"==typeof raw)try{const parsed=JSON.parse(raw);return function(value){if(!(0,import_core.isObject)(value))return!1;const candidate=value;return"string"==typeof candidate.client_email&&"string"==typeof candidate.private_key}(parsed)?parsed:void logger.throw("Invalid credentials JSON")}catch{return void logger.throw("Invalid credentials JSON")}return raw}}var import_core2=require("@walkeros/core");function jsonOrNull(value){return null==value?null:"object"==typeof value?Array.isArray(value)&&0===value.length?null:Array.isArray(value)||0!==Object.keys(value).length?JSON.stringify(value):null:JSON.stringify(value)}function eventToRow(event){return{name:event.name,data:jsonOrNull(event.data),context:jsonOrNull(event.context),globals:jsonOrNull(event.globals),custom:jsonOrNull(event.custom),user:jsonOrNull(event.user),nested:jsonOrNull(event.nested),consent:jsonOrNull(event.consent),id:event.id,trigger:event.trigger,entity:event.entity,action:event.action,timestamp:1e3*new Date(event.timestamp).getTime(),timing:event.timing,source:jsonOrNull(event.source)}}var import_bigquery_storage=require("@google-cloud/bigquery-storage");async function openWriter(args,logger){const{projectId:projectId,datasetId:datasetId,tableId:tableId,credentials:credentials,bigquery:bigquery,timeout:timeout,onConnectionError:onConnectionError}=args,destinationTable=`projects/${projectId}/datasets/${datasetId}/tables/${tableId}`;logger.debug("Opening BigQuery Storage Write API writer",{destinationTable:destinationTable});const callOptions=void 0===timeout?void 0:{timeout:timeout},writeClient=new import_bigquery_storage.managedwriter.WriterClient({projectId:projectId,...bigquery,...void 0!==credentials?{credentials:credentials}:{}});let connectionErrorListener,connection;try{connection=await writeClient.createStreamConnection({destinationTable:destinationTable,streamId:import_bigquery_storage.managedwriter.DefaultStream},callOptions),onConnectionError&&(connectionErrorListener=connection.onConnectionError(err=>{onConnectionError(err)}));const streamId=connection.getStreamId(),writeStream=await writeClient.getWriteStream({streamId:streamId,view:import_bigquery_storage.protos.google.cloud.bigquery.storage.v1.WriteStreamView.FULL},callOptions);if(!writeStream.tableSchema)throw new Error(`BigQuery write stream ${streamId} returned no tableSchema; cannot build proto descriptor`);const protoDescriptor=import_bigquery_storage.adapt.convertStorageSchemaToProto2Descriptor(writeStream.tableSchema,"root");return{writeClient:writeClient,writer:new import_bigquery_storage.managedwriter.JSONWriter({connection:connection,protoDescriptor:protoDescriptor}),connection:connection,connectionErrorListener:connectionErrorListener}}catch(err){throw closeWriter({writeClient:writeClient,connectionErrorListener:connectionErrorListener},logger),err}}function ensureWriter(settings,logger){if(!settings.writerBroken)return Promise.resolve();if(settings.reopenInFlight)return settings.reopenInFlight;const lastError=settings.lastStreamError;logger.info("BigQuery writer broken; attempting one re-open before failing",{error:lastError?lastError.message:"unknown stream error"});const reopen=settings.reopenWriter;if(!reopen)return Promise.reject(new Error("BigQuery writer is broken and no re-open hook is configured: "+(lastError?lastError.message:"unknown stream error")));const inFlight=(async()=>{try{closeWriter({writer:settings.writer,writeClient:settings.writeClient,connectionErrorListener:settings.connectionErrorListener},logger);const handles=await reopen();settings.writeClient=handles.writeClient,settings.writer=handles.writer,settings.connection=handles.connection,settings.connectionErrorListener=handles.connectionErrorListener,settings.writerBroken=!1,settings.lastStreamError=void 0,logger.info("BigQuery writer re-opened after a stream error")}finally{settings.reopenInFlight=void 0}})();return settings.reopenInFlight=inFlight,inFlight}function closeWriter(handles,logger){try{handles.connectionErrorListener?.off()}catch(err){logger.warn("connection error listener removal failed",{error:String(err)})}try{handles.writer?.close()}catch(err){logger.warn("writer.close failed",{error:String(err)})}try{handles.writeClient?.close()}catch(err){logger.warn("writeClient.close failed",{error:String(err)})}}var import_core3=require("@walkeros/core"),import_core4=require("@walkeros/core"),DEFAULT_SETUP={location:"EU",storageBillingModel:"PHYSICAL",partitioning:{type:"DAY",field:"timestamp"},clustering:{fields:["name","entity","action"]},schema:[{name:"name",type:"STRING",mode:"REQUIRED"},{name:"data",type:"JSON",mode:"NULLABLE"},{name:"context",type:"JSON",mode:"NULLABLE"},{name:"globals",type:"JSON",mode:"NULLABLE"},{name:"custom",type:"JSON",mode:"NULLABLE"},{name:"user",type:"JSON",mode:"NULLABLE"},{name:"nested",type:"JSON",mode:"NULLABLE"},{name:"consent",type:"JSON",mode:"NULLABLE"},{name:"id",type:"STRING",mode:"NULLABLE"},{name:"trigger",type:"STRING",mode:"NULLABLE"},{name:"entity",type:"STRING",mode:"NULLABLE"},{name:"action",type:"STRING",mode:"NULLABLE"},{name:"timestamp",type:"TIMESTAMP",mode:"NULLABLE"},{name:"timing",type:"FLOAT64",mode:"NULLABLE"},{name:"source",type:"JSON",mode:"NULLABLE"}]};function isAlreadyExists(err){return function(err){return"object"==typeof err&&null!==err&&("code"in err&&"number"==typeof err.code)}(err)&&409===err.code}var types_exports={},destinationBigQuery={type:"gcp-bigquery",config:{},setup:async function(ctx){const{config:config,logger:logger}=ctx,merged=(0,import_core4.resolveSetup)(config.setup,DEFAULT_SETUP);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const options={location:merged.location??DEFAULT_SETUP.location,storageBillingModel:merged.storageBillingModel??DEFAULT_SETUP.storageBillingModel,partitioning:merged.partitioning??DEFAULT_SETUP.partitioning,clustering:merged.clustering??DEFAULT_SETUP.clustering,schema:merged.schema??DEFAULT_SETUP.schema};if(!config.settings)return void logger.throw("setup: settings missing");const{client:client,datasetId:datasetId,tableId:tableId}=config.settings;if(!client)return void logger.throw("setup: BigQuery client is missing");if(!datasetId)return void logger.throw("setup: datasetId is missing");if(!tableId)return void logger.throw("setup: tableId is missing");const dataset=client.dataset(datasetId);let datasetCreated=!1;const[datasetExists]=await dataset.exists();if(datasetExists)logger.debug("setup: dataset exists",{datasetId:datasetId});else try{await dataset.create({location:options.location,storageBillingModel:options.storageBillingModel}),datasetCreated=!0,logger.info("setup: dataset created",{datasetId:datasetId,location:options.location,storageBillingModel:options.storageBillingModel})}catch(err){if(!isAlreadyExists(err))throw err;logger.debug("setup: dataset already exists (race)",{datasetId:datasetId})}const table=dataset.table(tableId);let tableCreated=!1;const[tableExists]=await table.exists();if(tableExists)logger.debug("setup: table exists",{datasetId:datasetId,tableId:tableId}),await async function(table,options,logger){let metadata;try{const[meta]=await table.getMetadata();metadata=meta??{}}catch(err){return void logger.debug("setup: drift check failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}if(v=metadata.timePartitioning,"object"==typeof v&&null!==v){const actual=metadata.timePartitioning,declared=options.partitioning;actual.type===declared.type&&actual.field===declared.field||logger.warn("setup.drift",{field:"timePartitioning",declared:declared,actual:actual})}var v;if(function(v){return"object"==typeof v&&null!==v}(metadata.clustering)){const actualFields=metadata.clustering.fields??[],declaredFields=options.clustering.fields;(actualFields.length!==declaredFields.length||actualFields.some((f,i)=>f!==declaredFields[i]))&&logger.warn("setup.drift",{field:"clustering",declared:{fields:declaredFields},actual:{fields:actualFields}})}if(function(v){return"object"==typeof v&&null!==v}(metadata.schema)){const actualFields=metadata.schema.fields??[],declaredFields=options.schema;(actualFields.length!==declaredFields.length||declaredFields.some((d,i)=>{const a=actualFields[i];return!a||a.name!==d.name||a.type!==d.type||a.mode!==d.mode}))&&logger.warn("setup.drift",{field:"schema",declared:declaredFields.map(f=>({name:f.name,type:f.type,mode:f.mode})),actual:actualFields.map(f=>({name:f.name,type:f.type,mode:f.mode}))})}}(table,options,logger);else try{await table.create({schema:{fields:options.schema},timePartitioning:{type:options.partitioning.type,field:options.partitioning.field},clustering:{fields:options.clustering.fields}}),tableCreated=!0,logger.info("setup: table created",{datasetId:datasetId,tableId:tableId,partitioning:options.partitioning,clustering:options.clustering})}catch(err){if(!isAlreadyExists(err))throw err;logger.debug("setup: table already exists (race)",{tableId:tableId})}return{datasetCreated:datasetCreated,tableCreated:tableCreated}},async init({config:partialConfig,env:env,logger:logger,id:id,reportError:reportError}){const config=function(partialConfig={},env,logger){const settings=partialConfig.settings||{},{projectId:projectId,bigquery:bigquery}=settings;let{client:client,location:location,datasetId:datasetId,tableId:tableId}=settings;projectId||logger.throw("Config settings projectId missing"),location=location||"EU",datasetId=datasetId||"walkerOS",tableId=tableId||"events";const options={...bigquery||{}};options.projectId=projectId;const parsed=parseCredentials(partialConfig.credentials,logger),credentials=void 0!==parsed&&"string"!=typeof parsed?parsed:void 0;void 0!==credentials&&(options.credentials=credentials);const BigQueryClass=env?.BigQuery||import_bigquery.BigQuery;client=client||new BigQueryClass(options);const settingsConfig={...settings,client:client,projectId:projectId,location:location,datasetId:datasetId,tableId:tableId,...void 0!==credentials?{credentials:credentials}:{}};return{...partialConfig,settings:settingsConfig}}(partialConfig,env,logger),timeout=config.timeout&&config.timeout>0?config.timeout:1e4,{settings:settings}=config,onConnectionError=err=>{settings.writerBroken=!0,settings.lastStreamError=err instanceof Error?err:new Error(String(err)),reportError?.(err)};settings.reopenWriter=()=>openWriter({projectId:settings.projectId,datasetId:settings.datasetId,tableId:settings.tableId,credentials:settings.credentials,bigquery:settings.bigquery,timeout:timeout,onConnectionError:onConnectionError},logger);try{const{writer:writer,writeClient:writeClient,connection:connection,connectionErrorListener:connectionErrorListener}=await openWriter({projectId:settings.projectId,datasetId:settings.datasetId,tableId:settings.tableId,credentials:settings.credentials,bigquery:settings.bigquery,timeout:timeout,onConnectionError:onConnectionError},logger);settings.writer=writer,settings.writeClient=writeClient,settings.connection=connection,settings.connectionErrorListener=connectionErrorListener}catch(err){const message=err instanceof Error?err.message:String(err);if(function(err){if("object"!=typeof err||null===err)return!1;if(!("code"in err))return!1;const obj=err;return 5===obj.code||404===obj.code}(err)){const target=`${config.settings.datasetId}.${config.settings.tableId}`,project=config.settings.projectId;logger.error(`BigQuery dataset or table not found: project="${project}", target="${target}". Run "walkeros setup destination.${id}" to create them.`,{project:project,target:target,error:message})}else logger.error("BigQuery init failed",{error:message});throw err}return config},push:async(event,context)=>await async function(event,{config:config,rule:_rule,data:data,logger:logger}){const settings=config.settings;if(!settings)return logger.throw("settings missing, init() not run");if(!settings.writer)return logger.throw("writer is missing, init() not run");settings.writerBroken&&await ensureWriter(settings,logger);const{writer:writer,datasetId:datasetId,tableId:tableId}=settings;if(!writer)return logger.throw("writer is missing, init() not run");if(!datasetId)return logger.throw("datasetId is missing");if(!tableId)return logger.throw("tableId is missing");const rows=[(0,import_core2.isObject)(data)?data:eventToRow(event)];let result;logger.debug("Calling BigQuery Storage Write API",{dataset:datasetId,table:tableId,rowCount:rows.length});try{const pending=writer.appendRows(rows);result=await pending.getResult()}catch(err){throw logger.error("BigQuery row append threw",{error:err instanceof Error?err.message:String(err)}),err}if(result.rowErrors&&result.rowErrors.length>0){const first=result.rowErrors[0];return logger.throw(`BigQuery row append failed: code=${first.code} message=${first.message}`)}logger.debug("BigQuery Storage Write API response",{ok:!0,offset:result.appendResult?.offset?.value})}(event,context),pushBatch:async(batch,{config:config,logger:logger})=>{const settings=config.settings;if(!settings)return logger.throw("settings missing, init() not run");if(!settings.writer)return logger.throw("writer is missing, init() not run");settings.writerBroken&&await ensureWriter(settings,logger);const{writer:writer,datasetId:datasetId,tableId:tableId}=settings;if(!writer)return logger.throw("writer is missing, init() not run");if(!datasetId)return logger.throw("datasetId is missing");if(!tableId)return logger.throw("tableId is missing");const rows=batch.entries.map(e=>(0,import_core3.isObject)(e.data)?e.data:eventToRow(e.event));if(0!==rows.length)try{logger.debug("Calling BigQuery Storage Write API (batch)",{dataset:datasetId,table:tableId,rowCount:rows.length});const pending=writer.appendRows(rows),result=await pending.getResult();if(result.rowErrors&&result.rowErrors.length>0){const failed=result.rowErrors.length;logger.info("BigQuery batch had partial failures",{ok:rows.length-failed,failed:failed,rowCount:rows.length});for(const re of result.rowErrors)logger.error("BigQuery row append failed",{index:re.index,code:re.code,message:re.message});return{failed:result.rowErrors.map(re=>({index:Number(re.index),error:Object.assign(new Error(re.message??"BigQuery row error"),{code:re.code})}))}}logger.debug("BigQuery batch append ok",{ok:rows.length,failed:0,offset:result.appendResult?.offset?.value})}catch(err){throw logger.error("BigQuery batch append threw",{error:err instanceof Error?err.message:String(err)}),err}},async destroy({config:config,logger:logger}){config.settings&&closeWriter({writer:config.settings.writer,writeClient:config.settings.writeClient,connectionErrorListener:config.settings.connectionErrorListener},logger)}};var import_core5=require("@walkeros/core");function hasNumericCode(err){if("object"!=typeof err||null===err)return!1;if(!("code"in err))return!1;return"number"==typeof err.code}var push2=async function(event,{config:config,rule:rule,data:data,collector:collector,logger:logger,id:id}){const settings=config.settings;if(!function(value){if("object"!=typeof value||null===value)return!1;const candidate=value;return void 0!==candidate.client&&"string"==typeof candidate.projectId&&candidate.projectId.length>0&&"string"==typeof candidate.topic&&candidate.topic.length>0}(settings))return logger.throw("settings missing or incomplete, init() not run");const{client:client,projectId:projectId,topic:defaultTopic}=settings,ruleSettings=(value=rule?.settings,"object"==typeof value&&null!==value?rule.settings:{});var value;const topicName="string"==typeof ruleSettings.topic&&ruleSettings.topic.length>0?ruleSettings.topic:defaultTopic,orderingValue=void 0!==ruleSettings.orderingKey?ruleSettings.orderingKey:settings.orderingKey;let orderingKey;if(void 0!==orderingValue){const resolved=await(0,import_core5.getMappingValue)(event,orderingValue,{collector:collector});"string"==typeof resolved&&resolved.length>0?orderingKey=resolved:null!=resolved&&(orderingKey=String(resolved))}const topicHandle=client.topic(topicName,{messageOrdering:Boolean(orderingKey)}),body=(0,import_core5.isObject)(data)&&Object.keys(data).length>0?data:event,payload=Buffer.from(JSON.stringify(body)),baseAttrs=settings.attributes,ruleAttrs=ruleSettings.attributes,attributes=await async function(event,base,override,collector){const result={};let touched=!1;if(base)for(const[key,value]of Object.entries(base)){const resolved=await(0,import_core5.getMappingValue)(event,value,{collector:collector});"string"==typeof resolved?(result[key]=resolved,touched=!0):null!=resolved&&(result[key]=String(resolved),touched=!0)}if(override)for(const[key,value]of Object.entries(override)){const resolved=await(0,import_core5.getMappingValue)(event,value,{collector:collector});"string"==typeof resolved?(result[key]=resolved,touched=!0):null!=resolved&&(result[key]=String(resolved),touched=!0)}return touched?result:void 0}(event,baseAttrs,ruleAttrs,collector),message={data:payload};attributes&&function(value){if(!(0,import_core5.isObject)(value))return!1;const candidate=value;for(const key of Object.keys(candidate))if("string"!=typeof candidate[key])return!1;return!0}(attributes)&&(message.attributes=attributes),orderingKey&&(message.orderingKey=orderingKey),logger.debug("Pub/Sub publish",{topic:topicName,projectId:projectId,orderingKey:orderingKey,event:event.name});try{await topicHandle.publishMessage(message)}catch(err){if(orderingKey)try{topicHandle.resumePublishing(orderingKey)}catch(resumeErr){logger.debug("Pub/Sub resumePublishing failed",{orderingKey:orderingKey,error:resumeErr instanceof Error?resumeErr.message:String(resumeErr)})}throw!function(err){return hasNumericCode(err)&&(5===err.code||404===err.code)}(err)?!function(err){return hasNumericCode(err)&&(7===err.code||16===err.code||403===err.code||401===err.code)}(err)?logger.error("Pub/Sub publish failed",{topic:topicName,projectId:projectId,event:event.name,error:err instanceof Error?err.message:String(err)}):logger.error(`Pub/Sub publish denied for topic "${topicName}". Grant the runtime service account roles/pubsub.publisher on this topic.`,{topic:topicName,projectId:projectId,error:err instanceof Error?err.message:String(err)}):logger.error(`Pub/Sub topic "${topicName}" not found in project "${projectId}". Run "walkeros setup destination.${id}" to create it.`,{topic:topicName,projectId:projectId,error:err instanceof Error?err.message:String(err)}),err}},import_core6=require("@walkeros/core"),import_pubsub2=require("@google-cloud/pubsub"),DEFAULT_SETUP2={messageStoragePolicy:{allowedPersistenceRegions:["europe-west1","europe-west3","europe-west4"]}};async function safeClose(client,logger){try{await client.close()}catch(err){logger.debug("setup: client.close failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}}var types_exports2={},destinationPubSub={type:"gcp-pubsub",config:{},setup:async function(ctx){const{config:config,env:env,logger:logger}=ctx,merged=(0,import_core6.resolveSetup)(config.setup,DEFAULT_SETUP2);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const settings=config.settings;if(!settings)return void logger.throw("setup: settings missing");const projectId="string"==typeof settings.projectId?settings.projectId:"",topicName="string"==typeof settings.topic?settings.topic:"";if(!projectId)return void logger.throw("setup: projectId is missing");if(!topicName)return void logger.throw("setup: topic is missing");let createdClient=!1,client=settings.client;if(!client){const Constructor=env?.PubSub??import_pubsub2.PubSub,credentials=parseCredentials(resolveCredentials(config,logger),logger);client=new Constructor({projectId:projectId,...void 0!==credentials&&"string"!=typeof credentials?{credentials:credentials}:{},...settings.apiEndpoint?{apiEndpoint:settings.apiEndpoint}:{}}),createdClient=!0}const topic=client.topic(topicName);let topicCreated=!1;const[exists]=await topic.exists();if(exists)logger.debug("setup: topic exists",{topic:topicName}),await async function(topic,declared,logger){let metadata;try{const[meta]=await topic.getMetadata();metadata=meta??{}}catch(err){return void logger.debug("setup: drift check failed (non-fatal)",{error:err instanceof Error?err.message:String(err)})}if(declared.messageStoragePolicy){const declaredRegions=declared.messageStoragePolicy.allowedPersistenceRegions,actualRegions=metadata.messageStoragePolicy?.allowedPersistenceRegions??[];(declaredRegions.length!==actualRegions.length||declaredRegions.some((r,i)=>r!==actualRegions[i]))&&logger.warn("setup.drift",{field:"messageStoragePolicy.allowedPersistenceRegions",declared:declaredRegions,actual:actualRegions})}if(declared.messageRetentionDuration){const declaredSeconds=declared.messageRetentionDuration.seconds,actualRaw=metadata.messageRetentionDuration?.seconds,actualSeconds="number"==typeof actualRaw?actualRaw:"string"==typeof actualRaw?Number(actualRaw):void 0;actualSeconds!==declaredSeconds&&logger.warn("setup.drift",{field:"messageRetentionDuration.seconds",declared:declaredSeconds,actual:actualSeconds??null})}void 0!==declared.kmsKeyName&&metadata.kmsKeyName!==declared.kmsKeyName&&logger.warn("setup.drift",{field:"kmsKeyName",declared:declared.kmsKeyName,actual:metadata.kmsKeyName??null});if(declared.labels){const actualLabels=metadata.labels??{};for(const[key,declaredValue]of Object.entries(declared.labels)){const actualValue=actualLabels[key];actualValue!==declaredValue&&logger.warn("setup.drift",{field:`labels.${key}`,declared:declaredValue,actual:actualValue??null})}}}(topic,merged,logger);else{const metadata={name:`projects/${projectId}/topics/${topicName}`};merged.messageStoragePolicy&&(metadata.messageStoragePolicy=merged.messageStoragePolicy),merged.messageRetentionDuration&&(metadata.messageRetentionDuration={seconds:merged.messageRetentionDuration.seconds}),merged.kmsKeyName&&(metadata.kmsKeyName=merged.kmsKeyName),merged.labels&&(metadata.labels=merged.labels);try{await client.createTopic(metadata),topicCreated=!0,logger.info("setup: topic created",{topic:topicName,projectId:projectId,messageStoragePolicy:metadata.messageStoragePolicy})}catch(err){if(!function(err){return function(err){return"object"==typeof err&&null!==err&&"code"in err&&"number"==typeof err.code}(err)&&(6===err.code||409===err.code)}(err))throw createdClient&&await safeClose(client,logger),err;logger.debug("setup: topic already exists (race)",{topic:topicName})}}return createdClient&&await safeClose(client,logger),{topicCreated:topicCreated}},init:async({config:partialConfig,env:env,logger:logger})=>function(partialConfig={},env,logger){const settings=partialConfig.settings??{},projectId="string"==typeof settings.projectId?settings.projectId:"",topic="string"==typeof settings.topic?settings.topic:"";projectId||logger.throw("Config settings projectId missing"),topic||logger.throw("Config settings topic missing");const credentials=parseCredentials(resolveCredentials(partialConfig,logger),logger);let client=settings.client;client||(client=new(env?.PubSub??import_pubsub.PubSub)(function(input){const result={projectId:input.projectId};return void 0!==input.credentials&&"string"!=typeof input.credentials&&(result.credentials=input.credentials),input.apiEndpoint&&(result.apiEndpoint=input.apiEndpoint),result}({projectId:projectId,credentials:credentials,apiEndpoint:settings.apiEndpoint})));const settingsConfig={...settings,client:client,projectId:projectId,topic:topic,credentials:credentials};return{...partialConfig,settings:settingsConfig}}(partialConfig,env,logger),push:async(event,context)=>await push2(event,context),async destroy({config:config}){if(!config.settings)return;const{client:client}=config.settings;if(client)try{await client.close()}catch{}}};//# sourceMappingURL=index.js.map
|