@cravery/firebase 0.0.45 → 0.0.46

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 (72) hide show
  1. package/dist/converters/access/usage.d.ts.map +1 -1
  2. package/dist/converters/access/usage.js.map +1 -1
  3. package/dist/converters/equipment/content.d.ts +1 -1
  4. package/dist/converters/equipment/content.d.ts.map +1 -1
  5. package/dist/converters/equipment/content.js +2 -2
  6. package/dist/converters/equipment/content.js.map +1 -1
  7. package/dist/converters/equipment/meta.d.ts +1 -1
  8. package/dist/converters/equipment/meta.d.ts.map +1 -1
  9. package/dist/converters/equipment/meta.js +2 -2
  10. package/dist/converters/equipment/meta.js.map +1 -1
  11. package/dist/converters/index.d.ts +2 -1
  12. package/dist/converters/index.d.ts.map +1 -1
  13. package/dist/converters/index.js +2 -1
  14. package/dist/converters/index.js.map +1 -1
  15. package/dist/converters/ingredients/content.d.ts +1 -1
  16. package/dist/converters/ingredients/content.d.ts.map +1 -1
  17. package/dist/converters/ingredients/content.js +2 -2
  18. package/dist/converters/ingredients/content.js.map +1 -1
  19. package/dist/converters/ingredients/meta.d.ts +1 -1
  20. package/dist/converters/ingredients/meta.d.ts.map +1 -1
  21. package/dist/converters/ingredients/meta.js +2 -2
  22. package/dist/converters/ingredients/meta.js.map +1 -1
  23. package/dist/converters/interactions/cook.d.ts +4 -0
  24. package/dist/converters/interactions/cook.d.ts.map +1 -0
  25. package/dist/converters/interactions/cook.js +26 -0
  26. package/dist/converters/interactions/cook.js.map +1 -0
  27. package/dist/converters/interactions/index.d.ts +4 -0
  28. package/dist/converters/interactions/index.d.ts.map +1 -0
  29. package/dist/converters/interactions/index.js +20 -0
  30. package/dist/converters/interactions/index.js.map +1 -0
  31. package/dist/converters/interactions/rate.d.ts +4 -0
  32. package/dist/converters/interactions/rate.d.ts.map +1 -0
  33. package/dist/converters/interactions/rate.js +26 -0
  34. package/dist/converters/interactions/rate.js.map +1 -0
  35. package/dist/converters/interactions/save.d.ts +4 -0
  36. package/dist/converters/interactions/save.d.ts.map +1 -0
  37. package/dist/converters/interactions/save.js +26 -0
  38. package/dist/converters/interactions/save.js.map +1 -0
  39. package/dist/converters/limits/index.d.ts +2 -0
  40. package/dist/converters/limits/index.d.ts.map +1 -0
  41. package/dist/converters/limits/index.js +18 -0
  42. package/dist/converters/limits/index.js.map +1 -0
  43. package/dist/converters/limits/usage.d.ts +4 -0
  44. package/dist/converters/limits/usage.d.ts.map +1 -0
  45. package/dist/converters/limits/usage.js +21 -0
  46. package/dist/converters/limits/usage.js.map +1 -0
  47. package/dist/converters/subscriptions/subscription.d.ts.map +1 -1
  48. package/dist/converters/subscriptions/subscription.js +4 -4
  49. package/dist/converters/subscriptions/subscription.js.map +1 -1
  50. package/dist/converters/users/index.d.ts +0 -2
  51. package/dist/converters/users/index.d.ts.map +1 -1
  52. package/dist/converters/users/index.js +0 -2
  53. package/dist/converters/users/index.js.map +1 -1
  54. package/dist/lib/monitoring.d.ts +5 -4
  55. package/dist/lib/monitoring.d.ts.map +1 -1
  56. package/dist/lib/monitoring.js +93 -25
  57. package/dist/lib/monitoring.js.map +1 -1
  58. package/package.json +2 -2
  59. package/src/converters/equipment/content.ts +1 -1
  60. package/src/converters/equipment/meta.ts +1 -1
  61. package/src/converters/index.ts +2 -1
  62. package/src/converters/ingredients/content.ts +1 -1
  63. package/src/converters/ingredients/meta.ts +1 -1
  64. package/src/converters/interactions/cook.ts +36 -0
  65. package/src/converters/interactions/index.ts +3 -0
  66. package/src/converters/{users/profile.ts → interactions/rate.ts} +36 -36
  67. package/src/converters/{users/bookmark.ts → interactions/save.ts} +6 -6
  68. package/src/converters/{access → limits}/index.ts +1 -1
  69. package/src/converters/{access → limits}/usage.ts +24 -28
  70. package/src/converters/subscriptions/subscription.ts +4 -2
  71. package/src/converters/users/index.ts +0 -2
  72. package/src/lib/monitoring.ts +121 -33
@@ -4,50 +4,112 @@ import {
4
4
  } from "@opentelemetry/sdk-metrics";
5
5
  import { resourceFromAttributes } from "@opentelemetry/resources";
6
6
  import { MetricExporter } from "@google-cloud/opentelemetry-cloud-monitoring-exporter";
7
- import { Counter, Histogram, ObservableGauge, Meter } from "@opentelemetry/api";
7
+ import {
8
+ Counter,
9
+ Histogram,
10
+ ObservableGauge,
11
+ Meter,
12
+ Attributes as OtelAttributes,
13
+ } from "@opentelemetry/api";
14
+
15
+ const METRIC_NAME_REGEX = /^[a-zA-Z][a-zA-Z0-9_.-]*$/;
16
+ const MAX_CACHED_METRICS = 1000;
17
+
18
+ const validateMetricName = (name: string): void => {
19
+ if (!METRIC_NAME_REGEX.test(name)) {
20
+ throw new Error(
21
+ `Invalid metric name "${name}": must start with a letter and contain only alphanumeric characters, underscores, dots, or hyphens`,
22
+ );
23
+ }
24
+ };
8
25
 
9
26
  const resource = resourceFromAttributes({
10
27
  "service.name": "cravery-backend",
11
28
  });
12
29
 
13
- const exporter = new MetricExporter();
14
-
15
- const meterProvider = new MeterProvider({
16
- resource,
17
- readers: [
18
- new PeriodicExportingMetricReader({
19
- exporter,
20
- exportIntervalMillis: 60000,
21
- }),
22
- ],
23
- });
30
+ let meterProvider: MeterProvider | null = null;
31
+ let meter: Meter | null = null;
32
+ let isShutdown = false;
33
+
34
+ const initializeProvider = (): void => {
35
+ if (meterProvider) return;
36
+
37
+ try {
38
+ const exporter = new MetricExporter();
39
+ meterProvider = new MeterProvider({
40
+ resource,
41
+ readers: [
42
+ new PeriodicExportingMetricReader({
43
+ exporter,
44
+ exportIntervalMillis: 60000,
45
+ }),
46
+ ],
47
+ });
48
+ meter = meterProvider.getMeter("cravery");
49
+ } catch (error) {
50
+ console.error("Failed to initialize metrics provider:", error);
51
+ throw error;
52
+ }
53
+ };
24
54
 
25
- const meter: Meter = meterProvider.getMeter("cravery");
55
+ const getMeter = (): Meter => {
56
+ if (isShutdown) {
57
+ throw new Error("Metrics provider has been shut down");
58
+ }
59
+ if (!meter) {
60
+ initializeProvider();
61
+ }
62
+ return meter!;
63
+ };
26
64
 
27
65
  const counters = new Map<string, Counter>();
28
66
  const histograms = new Map<string, Histogram>();
67
+ const gauges = new Map<string, ObservableGauge>();
29
68
 
30
69
  const getCounter = (name: string, description?: string): Counter => {
70
+ validateMetricName(name);
71
+ if (counters.size >= MAX_CACHED_METRICS && !counters.has(name)) {
72
+ throw new Error(
73
+ `Maximum number of cached counters (${MAX_CACHED_METRICS}) exceeded. Avoid dynamic metric names.`,
74
+ );
75
+ }
31
76
  if (!counters.has(name)) {
32
- counters.set(name, meter.createCounter(`cravery.${name}`, { description }));
77
+ counters.set(
78
+ name,
79
+ getMeter().createCounter(`cravery.${name}`, { description }),
80
+ );
33
81
  }
34
82
  return counters.get(name)!;
35
83
  };
36
84
 
37
- const getHistogram = (name: string, description?: string): Histogram => {
85
+ interface HistogramOptions {
86
+ description?: string;
87
+ unit?: string;
88
+ }
89
+
90
+ const getHistogram = (
91
+ name: string,
92
+ options: HistogramOptions = {},
93
+ ): Histogram => {
94
+ validateMetricName(name);
95
+ if (histograms.size >= MAX_CACHED_METRICS && !histograms.has(name)) {
96
+ throw new Error(
97
+ `Maximum number of cached histograms (${MAX_CACHED_METRICS}) exceeded. Avoid dynamic metric names.`,
98
+ );
99
+ }
38
100
  if (!histograms.has(name)) {
39
101
  histograms.set(
40
102
  name,
41
- meter.createHistogram(`cravery.${name}`, {
42
- description,
43
- unit: "ms",
103
+ getMeter().createHistogram(`cravery.${name}`, {
104
+ description: options.description,
105
+ unit: options.unit ?? "ms",
44
106
  }),
45
107
  );
46
108
  }
47
109
  return histograms.get(name)!;
48
110
  };
49
111
 
50
- type Attributes = Record<string, string>;
112
+ type Attributes = OtelAttributes;
51
113
 
52
114
  export const incrementCounter = (
53
115
  name: string,
@@ -71,21 +133,34 @@ export const withTiming = async <T>(
71
133
  attributes?: Attributes,
72
134
  ): Promise<T> => {
73
135
  const start = Date.now();
74
- const result = await fn();
75
- recordTiming(name, Date.now() - start, attributes);
76
- return result;
136
+ try {
137
+ return await fn();
138
+ } finally {
139
+ recordTiming(name, Date.now() - start, attributes);
140
+ }
77
141
  };
78
142
 
79
143
  export const registerGauge = (
80
144
  name: string,
81
- callback: () => number | Promise<number>,
145
+ callback: () => number,
82
146
  attributes?: Attributes,
83
147
  ): ObservableGauge => {
84
- const gauge = meter.createObservableGauge(`cravery.${name}`);
85
- gauge.addCallback(async (observableResult) => {
86
- const value = await callback();
87
- observableResult.observe(value, attributes);
148
+ validateMetricName(name);
149
+ if (gauges.has(name)) {
150
+ throw new Error(
151
+ `Gauge "${name}" is already registered. Gauges cannot be re-registered.`,
152
+ );
153
+ }
154
+ const gauge = getMeter().createObservableGauge(`cravery.${name}`);
155
+ gauge.addCallback((observableResult) => {
156
+ try {
157
+ const value = callback();
158
+ observableResult.observe(value, attributes);
159
+ } catch (error) {
160
+ console.error(`Error in gauge callback for "${name}":`, error);
161
+ }
88
162
  });
163
+ gauges.set(name, gauge);
89
164
  return gauge;
90
165
  };
91
166
 
@@ -102,11 +177,24 @@ export const createMetricsScope = (defaultAttributes: Attributes) => ({
102
177
  attributes?: Attributes,
103
178
  ) => withTiming(name, fn, { ...defaultAttributes, ...attributes }),
104
179
 
105
- registerGauge: (
106
- name: string,
107
- callback: () => number | Promise<number>,
108
- attributes?: Attributes,
109
- ) => registerGauge(name, callback, { ...defaultAttributes, ...attributes }),
180
+ registerGauge: (name: string, callback: () => number, attributes?: Attributes) =>
181
+ registerGauge(name, callback, { ...defaultAttributes, ...attributes }),
110
182
  });
111
183
 
112
- export const shutdown = (): Promise<void> => meterProvider.shutdown();
184
+ export const forceFlush = async (): Promise<void> => {
185
+ if (!meterProvider || isShutdown) {
186
+ return;
187
+ }
188
+ await meterProvider.forceFlush();
189
+ };
190
+
191
+ export const shutdown = async (): Promise<void> => {
192
+ if (!meterProvider || isShutdown) {
193
+ return;
194
+ }
195
+ isShutdown = true;
196
+ await meterProvider.shutdown();
197
+ counters.clear();
198
+ histograms.clear();
199
+ gauges.clear();
200
+ };