@firebase-function-kits/firestore-bigquery-export 0.0.1 → 0.0.2-rc.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.
Files changed (57) hide show
  1. package/CHANGELOG.md +1 -0
  2. package/README.md +303 -0
  3. package/lib/config.d.ts +33 -0
  4. package/lib/config.d.ts.map +1 -0
  5. package/lib/config.js +569 -0
  6. package/lib/config.js.map +1 -0
  7. package/lib/events.d.ts +45 -0
  8. package/lib/events.d.ts.map +1 -0
  9. package/lib/events.js +154 -0
  10. package/lib/events.js.map +1 -0
  11. package/lib/export-config.d.ts +96 -0
  12. package/lib/export-config.d.ts.map +1 -0
  13. package/lib/export-config.js +77 -0
  14. package/lib/export-config.js.map +1 -0
  15. package/lib/handlers.d.ts +29 -0
  16. package/lib/handlers.d.ts.map +1 -0
  17. package/lib/handlers.js +165 -0
  18. package/lib/handlers.js.map +1 -0
  19. package/lib/index.d.ts +23 -0
  20. package/lib/index.d.ts.map +1 -0
  21. package/lib/index.js +179 -0
  22. package/lib/index.js.map +1 -0
  23. package/lib/init.d.ts +16 -0
  24. package/lib/init.d.ts.map +1 -0
  25. package/lib/init.js +44 -0
  26. package/lib/init.js.map +1 -0
  27. package/lib/lib.d.ts +20 -0
  28. package/lib/lib.d.ts.map +1 -0
  29. package/lib/lib.js +47 -0
  30. package/lib/lib.js.map +1 -0
  31. package/lib/logs.d.ts +39 -0
  32. package/lib/logs.d.ts.map +1 -0
  33. package/lib/logs.js +186 -0
  34. package/lib/logs.js.map +1 -0
  35. package/lib/util.d.ts +23 -0
  36. package/lib/util.d.ts.map +1 -0
  37. package/lib/util.js +66 -0
  38. package/lib/util.js.map +1 -0
  39. package/npm-shrinkwrap.json +7016 -0
  40. package/package.json +37 -3
  41. package/src/config.ts +696 -0
  42. package/src/events.ts +146 -0
  43. package/src/export-config.ts +205 -0
  44. package/src/handlers.ts +211 -0
  45. package/src/index.ts +174 -0
  46. package/src/init.ts +45 -0
  47. package/src/lib.ts +55 -0
  48. package/src/logs.ts +233 -0
  49. package/src/util.ts +64 -0
  50. package/tests/config.test.ts +182 -0
  51. package/tests/events.test.ts +84 -0
  52. package/tests/export-config.test.ts +114 -0
  53. package/tests/handlers.test.ts +220 -0
  54. package/tests/init.test.ts +76 -0
  55. package/tests/util.test.ts +102 -0
  56. package/tsconfig.json +18 -0
  57. package/tsconfig.tsbuildinfo +1 -0
package/src/index.ts ADDED
@@ -0,0 +1,174 @@
1
+ /*
2
+ * Copyright 2019 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Main entry point. Exports the wired functions with deploy-time param
19
+ * expressions, then resolves concrete config lazily at runtime. Re-export
20
+ * `fsexportbigquery` and `initBigQuerySync` from your own functions codebase
21
+ * entry; configuration comes from a `.env` (or
22
+ * `.env.<projectId>`), which the Firebase CLI loads at deploy.
23
+ *
24
+ * Because this module initializes runtime dependencies lazily, deploy discovery
25
+ * can analyze it without resolving params too early.
26
+ * For side-effect-free imports (the handlers and config types), import from
27
+ * `./lib` instead.
28
+ */
29
+
30
+ import { FirestoreBigQueryEventHistoryTracker } from "@firebaseextensions/firestore-bigquery-change-tracker";
31
+ import { getApps, initializeApp } from "firebase-admin/app";
32
+ import { onDocumentWritten } from "firebase-functions/firestore";
33
+ import { expr } from "firebase-functions/params";
34
+ import { onTaskDispatched } from "firebase-functions/tasks";
35
+ import type { Role } from "firebase-functions/v2";
36
+ import { requiresAPI, requiresRole } from "firebase-functions/v2";
37
+ import {
38
+ afterFirstDeploy,
39
+ afterRedeploy,
40
+ } from "firebase-functions/v2/lifecycle";
41
+ import { CONFIG_EXPRESSIONS, configFromEnv } from "./config";
42
+ import * as events from "./events";
43
+ import { resolveExportConfig, toTrackerConfig } from "./export-config";
44
+ import { type HandlerContext, handleDocumentWrite } from "./handlers";
45
+ import { createEnsureInitialized } from "./init";
46
+ import * as logs from "./logs";
47
+
48
+ // Re-export the side-effect-free library surface (handlers and config types).
49
+ export * from "./lib";
50
+
51
+ const INIT_BIGQUERY_SYNC_FUNCTION = "initBigQuerySync";
52
+ const SETUP_BIGQUERY_SYNC_FUNCTION = "setupBigQuerySync";
53
+ const LIFECYCLE_RETRY_CONFIG = {
54
+ maxAttempts: 15,
55
+ minBackoffSeconds: 60,
56
+ } as const;
57
+ const REQUIRED_ROLES: ReadonlyArray<Role> = [
58
+ "roles/bigquery.dataEditor",
59
+ "roles/datastore.user",
60
+ "roles/bigquery.user",
61
+ // Gen2 Firestore triggers need Eventarc receive and run.invoker on the function SA.
62
+ "roles/eventarc.eventReceiver",
63
+ "roles/run.invoker",
64
+ ];
65
+ const REQUIRED_APIS = [
66
+ {
67
+ api: "bigquery.googleapis.com",
68
+ reason: "Mirrors data from your Cloud Firestore collection in BigQuery.",
69
+ },
70
+ ] as const;
71
+
72
+ for (const role of REQUIRED_ROLES) {
73
+ requiresRole(role);
74
+ }
75
+
76
+ for (const { api, reason } of REQUIRED_APIS) {
77
+ requiresAPI(api, reason);
78
+ }
79
+
80
+ afterFirstDeploy({
81
+ task: { function: INIT_BIGQUERY_SYNC_FUNCTION, body: { data: {} } },
82
+ });
83
+ afterRedeploy({
84
+ task: { function: SETUP_BIGQUERY_SYNC_FUNCTION, body: { data: {} } },
85
+ });
86
+
87
+ let ctx: HandlerContext | undefined;
88
+
89
+ function getHandlerContext(): HandlerContext {
90
+ if (ctx) {
91
+ return ctx;
92
+ }
93
+
94
+ const config = resolveExportConfig(configFromEnv());
95
+ const tracker = new FirestoreBigQueryEventHistoryTracker(
96
+ toTrackerConfig(config)
97
+ );
98
+
99
+ logs.logger.setLogLevel(config.logLevel);
100
+ logs.init(config);
101
+
102
+ if (getApps().length === 0) {
103
+ initializeApp();
104
+ }
105
+
106
+ events.setupEventChannel();
107
+
108
+ const ensureInitialized = createEnsureInitialized(tracker);
109
+
110
+ ctx = {
111
+ tracker,
112
+ config,
113
+ ensureInitialized,
114
+ };
115
+
116
+ return ctx;
117
+ }
118
+
119
+ const functionOptions = {
120
+ region: CONFIG_EXPRESSIONS.location,
121
+ };
122
+
123
+ /**
124
+ * Firestore trigger: streams document writes on the watched collection into the
125
+ * BigQuery changelog table. Failed executions are retried by the Firebase
126
+ * Functions runtime.
127
+ */
128
+ export const fsexportbigquery = onDocumentWritten(
129
+ {
130
+ ...functionOptions,
131
+ document: expr`${CONFIG_EXPRESSIONS.collectionPath}/{documentId}`,
132
+ database: CONFIG_EXPRESSIONS.database,
133
+ retry: true,
134
+ },
135
+ (event) => handleDocumentWrite(event, getHandlerContext())
136
+ );
137
+
138
+ async function handleBigQuerySyncInitialization(): Promise<void> {
139
+ try {
140
+ await getHandlerContext().ensureInitialized();
141
+ } catch (err) {
142
+ logs.error(false, "Failed to initialize BigQuery resources", err);
143
+ throw err;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * First-deploy provisioning task (`afterFirstDeploy`). Runs in the function's
149
+ * own identity, so it has the runtime service account and bound secrets that
150
+ * creating the dataset/table/views needs (e.g. CMEK). Cloud Tasks retries a
151
+ * failed initialization on its own schedule, so a transient BigQuery error does
152
+ * not leave the resources unprovisioned. It is idempotent (`initialize()`
153
+ * no-ops when resources already exist) and can also be invoked directly as an
154
+ * authenticated HTTP POST, without queue retries.
155
+ */
156
+ export const initBigQuerySync = onTaskDispatched(
157
+ {
158
+ ...functionOptions,
159
+ retryConfig: LIFECYCLE_RETRY_CONFIG,
160
+ },
161
+ handleBigQuerySyncInitialization
162
+ );
163
+
164
+ /**
165
+ * Update/configure lifecycle task. Uses the same idempotent provisioning path
166
+ * so BigQuery resources are reconciled after parameter changes.
167
+ */
168
+ export const setupBigQuerySync = onTaskDispatched(
169
+ {
170
+ ...functionOptions,
171
+ retryConfig: LIFECYCLE_RETRY_CONFIG,
172
+ },
173
+ handleBigQuerySyncInitialization
174
+ );
package/src/init.ts ADDED
@@ -0,0 +1,45 @@
1
+ /*
2
+ * Copyright 2019 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import type { FirestoreBigQueryEventHistoryTracker } from "@firebaseextensions/firestore-bigquery-change-tracker";
18
+
19
+ /**
20
+ * Builds the provisioning guard used by the `initBigQuerySync` endpoint and the
21
+ * retry-path self-heal. The hot write path never calls it.
22
+ *
23
+ * The returned function runs `tracker.initialize()` at most once per instance:
24
+ * concurrent invocations on a cold instance share a single in-flight promise. A
25
+ * failed initialization resets the guard so a later invocation can retry after
26
+ * a transient error.
27
+ *
28
+ * @param tracker - The event history tracker whose `initialize()` provisions
29
+ * the BigQuery dataset, table, and views.
30
+ * @returns A function that resolves once the resources are provisioned.
31
+ */
32
+ export function createEnsureInitialized(
33
+ tracker: FirestoreBigQueryEventHistoryTracker
34
+ ): () => Promise<void> {
35
+ let initialization: Promise<void> | null = null;
36
+ return () => {
37
+ if (!initialization) {
38
+ initialization = tracker.initialize().catch((err) => {
39
+ initialization = null;
40
+ throw err;
41
+ });
42
+ }
43
+ return initialization;
44
+ };
45
+ }
package/src/lib.ts ADDED
@@ -0,0 +1,55 @@
1
+ /*
2
+ * Copyright 2019 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ /**
18
+ * Side-effect-free library surface:
19
+ *
20
+ * - {@link handleDocumentWrite} — the handler, for consumers who want to own
21
+ * trigger registration themselves. It takes an injected
22
+ * {@link HandlerContext}.
23
+ * - Config types and helpers ({@link ExportConfig}, {@link resolveExportConfig},
24
+ * {@link toTrackerConfig}) for building that context.
25
+ *
26
+ * Importing this module has no side effects (it reads no environment), so it is
27
+ * safe to import anywhere. The main entry point (`./index`) is the one that
28
+ * reads env params and exports the wired functions.
29
+ *
30
+ * The change-tracker engine is an internal dependency and is deliberately not
31
+ * re-exported; only the types the handler signatures need are surfaced.
32
+ */
33
+
34
+ // Engine types referenced by the handler and config signatures. Type-only —
35
+ // the engine itself is internal. `ChangeType` is exported for handler tests and
36
+ // advanced consumers that build Firestore events directly.
37
+ export {
38
+ type ChangeTrackerConfig,
39
+ ChangeType,
40
+ type FirestoreBigQueryEventHistoryTracker,
41
+ } from "@firebaseextensions/firestore-bigquery-change-tracker";
42
+ // Config types and helpers
43
+ export {
44
+ type ExportConfig,
45
+ type ResolvedExportConfig,
46
+ resolveExportConfig,
47
+ toTrackerConfig,
48
+ type ViewType,
49
+ } from "./export-config";
50
+ // Handlers
51
+ export {
52
+ type DocumentWriteEvent,
53
+ type HandlerContext,
54
+ handleDocumentWrite,
55
+ } from "./handlers";
package/src/logs.ts ADDED
@@ -0,0 +1,233 @@
1
+ /*
2
+ * Copyright 2019 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ type ChangeType,
19
+ Logger,
20
+ } from "@firebaseextensions/firestore-bigquery-change-tracker";
21
+
22
+ export const logger = new Logger();
23
+
24
+ export const arrayFieldInvalid = (fieldName: string) => {
25
+ logger.warn(`Array field '${fieldName}' does not contain an array, skipping`);
26
+ };
27
+
28
+ export const bigQueryDatasetCreated = (datasetId: string) => {
29
+ logger.info(`Created BigQuery dataset: ${datasetId}`);
30
+ };
31
+
32
+ export const bigQueryDatasetCreating = (datasetId: string) => {
33
+ logger.debug(`Creating BigQuery dataset: ${datasetId}`);
34
+ };
35
+
36
+ export const bigQueryDatasetExists = (datasetId: string) => {
37
+ logger.info(`BigQuery dataset already exists: ${datasetId}`);
38
+ };
39
+
40
+ export const bigQueryErrorRecordingDocumentChange = (e: Error) => {
41
+ logger.error(`Error recording document changes.`, e);
42
+ };
43
+
44
+ export const bigQueryLatestSnapshotViewQueryCreated = (query: string) => {
45
+ logger.info(`BigQuery latest snapshot view query:\n${query}`);
46
+ };
47
+
48
+ export const bigQueryTableAlreadyExists = (
49
+ tableName: string,
50
+ datasetName: string
51
+ ) => {
52
+ logger.info(
53
+ `BigQuery table with name ${tableName} already ` +
54
+ `exists in dataset ${datasetName}!`
55
+ );
56
+ };
57
+
58
+ export const bigQueryTableCreated = (tableName: string) => {
59
+ logger.info(`Created BigQuery table: ${tableName}`);
60
+ };
61
+
62
+ export const bigQueryTableCreating = (tableName: string) => {
63
+ logger.debug(`Creating BigQuery table: ${tableName}`);
64
+ };
65
+
66
+ export const bigQueryTableUpdated = (tableName: string) => {
67
+ logger.info(`Updated existing BigQuery table: ${tableName}`);
68
+ };
69
+
70
+ export const bigQueryTableUpdating = (tableName: string) => {
71
+ logger.debug(`Updating existing BigQuery table: ${tableName}`);
72
+ };
73
+
74
+ export const bigQueryTableUpToDate = (tableName: string) => {
75
+ logger.info(`BigQuery table: ${tableName} is up to date`);
76
+ };
77
+
78
+ export const bigQueryTableValidated = (tableName: string) => {
79
+ logger.info(`Validated existing BigQuery table: ${tableName}`);
80
+ };
81
+
82
+ export const bigQueryTableValidating = (tableName: string) => {
83
+ logger.debug(`Validating existing BigQuery table: ${tableName}`);
84
+ };
85
+
86
+ export const bigQueryUserDefinedFunctionCreating = (
87
+ functionDefinition: string
88
+ ) => {
89
+ logger.debug(
90
+ `Creating BigQuery User-defined Function:\n${functionDefinition}`
91
+ );
92
+ };
93
+
94
+ export const bigQueryUserDefinedFunctionCreated = (
95
+ functionDefinition: string
96
+ ) => {
97
+ logger.info(`Created BigQuery User-defined Function:\n${functionDefinition}`);
98
+ };
99
+
100
+ export const bigQueryViewCreated = (viewName: string) => {
101
+ logger.info(`Created BigQuery view: ${viewName}`);
102
+ };
103
+
104
+ export const bigQueryViewCreating = (viewName: string) => {
105
+ logger.debug(`Creating BigQuery view: ${viewName}`);
106
+ };
107
+
108
+ export const bigQueryViewAlreadyExists = (
109
+ viewName: string,
110
+ datasetName: string
111
+ ) => {
112
+ logger.info(
113
+ `View with id ${viewName} already exists in dataset ${datasetName}.`
114
+ );
115
+ };
116
+
117
+ export const bigQueryViewUpdated = (viewName: string) => {
118
+ logger.info(`Updated existing BigQuery view: ${viewName}`);
119
+ };
120
+
121
+ export const bigQueryViewUpdating = (viewName: string) => {
122
+ logger.debug(`Updating existing BigQuery view: ${viewName}`);
123
+ };
124
+
125
+ export const bigQueryViewUpToDate = (viewName: string) => {
126
+ logger.info(`BigQuery view: ${viewName} is up to date`);
127
+ };
128
+
129
+ export const bigQueryViewValidated = (viewName: string) => {
130
+ logger.info(`Validated existing BigQuery view: ${viewName}`);
131
+ };
132
+
133
+ export const bigQueryViewValidating = (viewName: string) => {
134
+ logger.debug(`Validating existing BigQuery view: ${viewName}`);
135
+ };
136
+
137
+ export const complete = () => {
138
+ logger.info("Completed execution of extension");
139
+ };
140
+
141
+ export const dataInserted = (rowCount: number) => {
142
+ logger.debug(`Inserted ${rowCount} row(s) of data into BigQuery`);
143
+ };
144
+
145
+ export const dataInserting = (rowCount: number) => {
146
+ logger.debug(`Inserting ${rowCount} row(s) of data into BigQuery`);
147
+ };
148
+
149
+ export const dataTypeInvalid = (
150
+ fieldName: string,
151
+ fieldType: string,
152
+ dataType: string
153
+ ) => {
154
+ logger.warn(
155
+ `Field '${fieldName}' has invalid data. Expected: ${fieldType}, received: ${dataType}`
156
+ );
157
+ };
158
+
159
+ export const error = (
160
+ includeEvent: boolean,
161
+ message: string,
162
+ err: Error,
163
+ event?: any, // Made optional, as it is not always required
164
+ eventTrackerConfig?: any // Made optional, as it is not always required
165
+ ) => {
166
+ const logDetails: Record<string, any> = { error: err };
167
+
168
+ if (includeEvent && event) {
169
+ logDetails.event = event;
170
+ }
171
+
172
+ if (includeEvent && eventTrackerConfig) {
173
+ logDetails.eventTrackerConfig = eventTrackerConfig;
174
+ }
175
+
176
+ logger.error(`Error when mirroring data to BigQuery: ${message}`, logDetails);
177
+ };
178
+
179
+ export const init = (config?: unknown) => {
180
+ logger.info("Initializing with configuration", config);
181
+ };
182
+
183
+ export const start = (config?: unknown) => {
184
+ logger.info("Started execution with configuration", config);
185
+ };
186
+
187
+ export const timestampMissingValue = (fieldName: string) => {
188
+ logger.warn(
189
+ `Missing value for timestamp field: ${fieldName}, using default timestamp instead.`
190
+ );
191
+ };
192
+
193
+ export const logEventAction = (
194
+ action: string,
195
+ document_name: string,
196
+ event_id: string,
197
+ operation: ChangeType
198
+ ) => {
199
+ logger.info(action, {
200
+ document_name,
201
+ event_id,
202
+ operation,
203
+ });
204
+ };
205
+
206
+ export const logFailedEventAction = (
207
+ action: string,
208
+ document_name: string,
209
+ event_id: string,
210
+ operation: ChangeType,
211
+ error: Error
212
+ ) => {
213
+ const changeTypeMap = {
214
+ 0: "CREATE",
215
+ 1: "DELETE",
216
+ 2: "UPDATE",
217
+ 3: "IMPORT",
218
+ };
219
+
220
+ logger.error(action, {
221
+ document_name,
222
+ event_id,
223
+ operation: changeTypeMap[operation],
224
+ error,
225
+ });
226
+ };
227
+
228
+ export const failedToWriteToBigQueryImmediately = (error: Error) => {
229
+ logger.warn(
230
+ "Failed to write event to BigQuery Immediately. Will attempt to Enqueue to Cloud Tasks.",
231
+ error
232
+ );
233
+ };
package/src/util.ts ADDED
@@ -0,0 +1,64 @@
1
+ /*
2
+ * Copyright 2019 Google LLC
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * https://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import { ChangeType } from "@firebaseextensions/firestore-bigquery-change-tracker";
18
+ import type { Change, DocumentSnapshot } from "firebase-functions/firestore";
19
+
20
+ /**
21
+ * Get the change type (CREATE, UPDATE, DELETE) from the Firestore change.
22
+ * @param change Firestore document change object.
23
+ * @returns {ChangeType} The type of change.
24
+ */
25
+ export function getChangeType(change: Change<DocumentSnapshot>): ChangeType {
26
+ if (!change.after.exists) {
27
+ return ChangeType.DELETE;
28
+ }
29
+ if (!change.before.exists) {
30
+ return ChangeType.CREATE;
31
+ }
32
+ return ChangeType.UPDATE;
33
+ }
34
+
35
+ /**
36
+ * Get the document ID from the Firestore change.
37
+ * @param change Firestore document change object.
38
+ * @returns {string} The document ID.
39
+ */
40
+ export function getDocumentId(change: Change<DocumentSnapshot>): string {
41
+ if (change.after.exists) {
42
+ return change.after.id;
43
+ }
44
+ return change.before.id;
45
+ }
46
+
47
+ /**
48
+ *
49
+ * @param template - eg, regions/{regionId}/countries
50
+ * @param text - eg, regions/asia/countries
51
+ *
52
+ * @return - eg, { regionId: "asia" }
53
+ */
54
+ export const resolveWildcardIds = (template: string, text: string) => {
55
+ const textSegments = text.split("/");
56
+ return template
57
+ .split("/")
58
+ .reduce((previousValue, currentValue, currentIndex) => {
59
+ if (currentValue.startsWith("{") && currentValue.endsWith("}")) {
60
+ previousValue[currentValue.slice(1, -1)] = textSegments[currentIndex];
61
+ }
62
+ return previousValue;
63
+ }, {});
64
+ };