@terreno/api 0.13.2 → 0.14.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.
Files changed (175) hide show
  1. package/dist/__tests__/versionCheckPlugin.test.js +53 -3
  2. package/dist/api.arrayOperations.test.js +1 -0
  3. package/dist/api.asyncHandler.test.d.ts +1 -0
  4. package/dist/api.asyncHandler.test.js +236 -0
  5. package/dist/api.d.ts +15 -4
  6. package/dist/api.errors.test.js +1 -0
  7. package/dist/api.hooks.test.js +1 -0
  8. package/dist/api.js +153 -104
  9. package/dist/api.query.test.js +1 -0
  10. package/dist/api.test.js +174 -0
  11. package/dist/auth.d.ts +10 -5
  12. package/dist/auth.js +163 -90
  13. package/dist/auth.test.js +159 -0
  14. package/dist/betterAuthApp.test.js +1 -0
  15. package/dist/betterAuthSetup.d.ts +5 -6
  16. package/dist/betterAuthSetup.js +17 -14
  17. package/dist/betterAuthSetup.test.js +1 -0
  18. package/dist/config.d.ts +48 -0
  19. package/dist/config.js +248 -0
  20. package/dist/config.test.d.ts +1 -0
  21. package/dist/config.test.js +328 -0
  22. package/dist/configuration.test.js +1 -0
  23. package/dist/configurationApp.d.ts +1 -1
  24. package/dist/configurationApp.js +17 -13
  25. package/dist/configurationPlugin.test.js +1 -0
  26. package/dist/consentApp.test.js +1 -0
  27. package/dist/envConfigurationPlugin.d.ts +2 -0
  28. package/dist/envConfigurationPlugin.js +173 -0
  29. package/dist/envConfigurationPlugin.test.d.ts +1 -0
  30. package/dist/envConfigurationPlugin.test.js +322 -0
  31. package/dist/errors.d.ts +18 -7
  32. package/dist/errors.js +106 -10
  33. package/dist/errors.test.js +16 -1
  34. package/dist/example.js +16 -7
  35. package/dist/expressServer.d.ts +10 -9
  36. package/dist/expressServer.js +62 -53
  37. package/dist/expressServer.test.js +53 -2
  38. package/dist/githubAuth.d.ts +2 -1
  39. package/dist/githubAuth.js +41 -26
  40. package/dist/githubAuth.test.js +1 -0
  41. package/dist/index.d.ts +4 -0
  42. package/dist/index.js +4 -0
  43. package/dist/logger.d.ts +1 -1
  44. package/dist/logger.js +42 -20
  45. package/dist/models/versionConfig.d.ts +2 -0
  46. package/dist/models/versionConfig.js +8 -0
  47. package/dist/notifiers/googleChatNotifier.js +14 -16
  48. package/dist/notifiers/googleChatNotifier.test.js +1 -0
  49. package/dist/notifiers/slackNotifier.js +16 -14
  50. package/dist/notifiers/slackNotifier.test.js +41 -3
  51. package/dist/notifiers/zoomNotifier.js +7 -10
  52. package/dist/notifiers/zoomNotifier.test.js +1 -0
  53. package/dist/openApi.d.ts +1 -1
  54. package/dist/openApi.test.js +1 -0
  55. package/dist/openApiBuilder.d.ts +39 -6
  56. package/dist/openApiBuilder.js +1 -31
  57. package/dist/openApiBuilder.test.js +1 -0
  58. package/dist/openApiValidator.js +1 -0
  59. package/dist/openApiValidator.test.js +65 -0
  60. package/dist/permissions.d.ts +4 -4
  61. package/dist/permissions.js +67 -65
  62. package/dist/permissions.middleware.test.js +1 -0
  63. package/dist/permissions.test.js +1 -0
  64. package/dist/plugins.d.ts +5 -5
  65. package/dist/plugins.js +18 -9
  66. package/dist/plugins.test.js +1 -1
  67. package/dist/populate.d.ts +15 -8
  68. package/dist/populate.js +23 -24
  69. package/dist/populate.test.js +1 -0
  70. package/dist/realtime/changeStreamWatcher.d.ts +73 -0
  71. package/dist/realtime/changeStreamWatcher.js +720 -0
  72. package/dist/realtime/index.d.ts +6 -0
  73. package/dist/realtime/index.js +27 -0
  74. package/dist/realtime/queryMatcher.d.ts +14 -0
  75. package/dist/realtime/queryMatcher.js +250 -0
  76. package/dist/realtime/queryStore.d.ts +37 -0
  77. package/dist/realtime/queryStore.js +195 -0
  78. package/dist/realtime/realtime.test.d.ts +10 -0
  79. package/dist/realtime/realtime.test.js +2158 -0
  80. package/dist/realtime/realtimeApp.d.ts +93 -0
  81. package/dist/realtime/realtimeApp.js +560 -0
  82. package/dist/realtime/registry.d.ts +40 -0
  83. package/dist/realtime/registry.js +38 -0
  84. package/dist/realtime/socketUser.d.ts +10 -0
  85. package/dist/realtime/socketUser.js +17 -0
  86. package/dist/realtime/types.d.ts +100 -0
  87. package/dist/realtime/types.js +2 -0
  88. package/dist/requestContext.d.ts +37 -0
  89. package/dist/requestContext.js +344 -0
  90. package/dist/requestContext.test.d.ts +1 -0
  91. package/dist/requestContext.test.js +241 -0
  92. package/dist/terrenoApp.d.ts +8 -0
  93. package/dist/terrenoApp.js +50 -13
  94. package/dist/terrenoApp.test.js +194 -21
  95. package/dist/terrenoPlugin.d.ts +11 -0
  96. package/dist/tests/bunSetup.js +1 -0
  97. package/dist/tests.js +1 -1
  98. package/dist/transformers.d.ts +2 -2
  99. package/dist/transformers.js +5 -3
  100. package/dist/transformers.test.js +90 -0
  101. package/dist/types/consentResponse.d.ts +6 -3
  102. package/dist/versionCheckPlugin.d.ts +2 -0
  103. package/dist/versionCheckPlugin.js +18 -12
  104. package/package.json +4 -2
  105. package/src/__tests__/versionCheckPlugin.test.ts +37 -3
  106. package/src/api.arrayOperations.test.ts +1 -0
  107. package/src/api.asyncHandler.test.ts +177 -0
  108. package/src/api.errors.test.ts +1 -0
  109. package/src/api.hooks.test.ts +1 -0
  110. package/src/api.query.test.ts +1 -0
  111. package/src/api.test.ts +132 -0
  112. package/src/api.ts +199 -84
  113. package/src/auth.test.ts +160 -0
  114. package/src/auth.ts +120 -50
  115. package/src/betterAuthApp.test.ts +1 -0
  116. package/src/betterAuthSetup.test.ts +1 -0
  117. package/src/betterAuthSetup.ts +46 -19
  118. package/src/config.test.ts +255 -0
  119. package/src/config.ts +206 -0
  120. package/src/configuration.test.ts +1 -0
  121. package/src/configurationApp.ts +59 -24
  122. package/src/configurationPlugin.test.ts +1 -0
  123. package/src/consentApp.test.ts +1 -0
  124. package/src/envConfigurationPlugin.test.ts +143 -0
  125. package/src/envConfigurationPlugin.ts +100 -0
  126. package/src/errors.test.ts +19 -1
  127. package/src/errors.ts +94 -20
  128. package/src/example.ts +46 -21
  129. package/src/express.d.ts +18 -1
  130. package/src/expressServer.test.ts +50 -2
  131. package/src/expressServer.ts +80 -50
  132. package/src/githubAuth.test.ts +1 -0
  133. package/src/githubAuth.ts +59 -38
  134. package/src/index.ts +4 -0
  135. package/src/logger.ts +47 -17
  136. package/src/models/versionConfig.ts +13 -2
  137. package/src/notifiers/googleChatNotifier.test.ts +1 -0
  138. package/src/notifiers/googleChatNotifier.ts +7 -9
  139. package/src/notifiers/slackNotifier.test.ts +29 -3
  140. package/src/notifiers/slackNotifier.ts +9 -7
  141. package/src/notifiers/zoomNotifier.test.ts +1 -0
  142. package/src/notifiers/zoomNotifier.ts +8 -11
  143. package/src/openApi.test.ts +1 -0
  144. package/src/openApi.ts +4 -4
  145. package/src/openApiBuilder.test.ts +1 -0
  146. package/src/openApiBuilder.ts +14 -11
  147. package/src/openApiValidator.test.ts +59 -0
  148. package/src/openApiValidator.ts +3 -2
  149. package/src/permissions.middleware.test.ts +1 -0
  150. package/src/permissions.test.ts +1 -0
  151. package/src/permissions.ts +30 -25
  152. package/src/plugins.test.ts +1 -1
  153. package/src/plugins.ts +21 -14
  154. package/src/populate.test.ts +1 -0
  155. package/src/populate.ts +44 -36
  156. package/src/realtime/changeStreamWatcher.ts +568 -0
  157. package/src/realtime/index.ts +34 -0
  158. package/src/realtime/queryMatcher.ts +179 -0
  159. package/src/realtime/queryStore.ts +132 -0
  160. package/src/realtime/realtime.test.ts +1755 -0
  161. package/src/realtime/realtimeApp.ts +478 -0
  162. package/src/realtime/registry.ts +64 -0
  163. package/src/realtime/socketUser.ts +25 -0
  164. package/src/realtime/types.ts +112 -0
  165. package/src/requestContext.test.ts +196 -0
  166. package/src/requestContext.ts +368 -0
  167. package/src/terrenoApp.test.ts +137 -11
  168. package/src/terrenoApp.ts +64 -17
  169. package/src/terrenoPlugin.ts +12 -0
  170. package/src/tests/bunSetup.ts +1 -0
  171. package/src/tests.ts +7 -2
  172. package/src/transformers.test.ts +70 -2
  173. package/src/transformers.ts +15 -7
  174. package/src/types/consentResponse.ts +8 -10
  175. package/src/versionCheckPlugin.ts +15 -7
@@ -0,0 +1,179 @@
1
+ // biome-ignore-all lint/suspicious/noExplicitAny: MongoDB query matcher evaluates dynamic filter shapes
2
+ /**
3
+ * Simple in-memory MongoDB query matcher.
4
+ * Evaluates a MongoDB-style query object against a document without hitting the database.
5
+ *
6
+ * Supports: equality, $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $and, $or, $not.
7
+ */
8
+
9
+ // biome-ignore lint/suspicious/noExplicitAny: traversing arbitrary nested document fields by user-supplied dotted path
10
+ const getNestedValue = (doc: any, path: string): any => {
11
+ const parts = path.split(".");
12
+ let current = doc;
13
+ for (const part of parts) {
14
+ if (current === null || current === undefined) {
15
+ return undefined;
16
+ }
17
+ current = current[part];
18
+ }
19
+ return current;
20
+ };
21
+
22
+ // biome-ignore lint/suspicious/noExplicitAny: value may be any document field type (string, number, ObjectId, etc.)
23
+ const normalize = (value: any): any => {
24
+ if (value === null || value === undefined) {
25
+ return value;
26
+ }
27
+ // Handle ObjectId-like objects with toString
28
+ if (
29
+ typeof value === "object" &&
30
+ typeof value.toString === "function" &&
31
+ value.constructor?.name !== "Object" &&
32
+ !Array.isArray(value)
33
+ ) {
34
+ return value.toString();
35
+ }
36
+ return value;
37
+ };
38
+
39
+ // biome-ignore lint/suspicious/noExplicitAny: rawValue is an arbitrary document field, condition is an arbitrary user query operand
40
+ const matchesCondition = (rawValue: any, condition: any): boolean => {
41
+ const value = normalize(rawValue);
42
+
43
+ // Direct equality (non-object condition)
44
+ if (condition === null || condition === undefined || typeof condition !== "object") {
45
+ const normalizedCondition = normalize(condition);
46
+ return value === normalizedCondition || String(value) === String(normalizedCondition);
47
+ }
48
+
49
+ // Array equality
50
+ if (Array.isArray(condition)) {
51
+ return JSON.stringify(value) === JSON.stringify(condition);
52
+ }
53
+
54
+ // Operator object
55
+ for (const [op, operand] of Object.entries(condition)) {
56
+ const normOp = normalize(operand);
57
+
58
+ switch (op) {
59
+ case "$eq":
60
+ if (value !== normOp && String(value) !== String(normOp)) {
61
+ return false;
62
+ }
63
+ break;
64
+ case "$ne":
65
+ if (value === normOp || String(value) === String(normOp)) {
66
+ return false;
67
+ }
68
+ break;
69
+ case "$gt":
70
+ if (!(value > normOp)) {
71
+ return false;
72
+ }
73
+ break;
74
+ case "$gte":
75
+ if (!(value >= normOp)) {
76
+ return false;
77
+ }
78
+ break;
79
+ case "$lt":
80
+ if (!(value < normOp)) {
81
+ return false;
82
+ }
83
+ break;
84
+ case "$lte":
85
+ if (!(value <= normOp)) {
86
+ return false;
87
+ }
88
+ break;
89
+ case "$in": {
90
+ if (!Array.isArray(operand)) {
91
+ return false;
92
+ }
93
+ const inValues = operand.map(normalize);
94
+ // biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
95
+ if (!inValues.some((v: any) => v === value || String(v) === String(value))) {
96
+ return false;
97
+ }
98
+ break;
99
+ }
100
+ case "$nin": {
101
+ if (!Array.isArray(operand)) {
102
+ return false;
103
+ }
104
+ const ninValues = operand.map(normalize);
105
+ // biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
106
+ if (ninValues.some((v: any) => v === value || String(v) === String(value))) {
107
+ return false;
108
+ }
109
+ break;
110
+ }
111
+ case "$exists":
112
+ if (operand && rawValue === undefined) {
113
+ return false;
114
+ }
115
+ if (!operand && rawValue !== undefined) {
116
+ return false;
117
+ }
118
+ break;
119
+ case "$not":
120
+ if (matchesCondition(rawValue, operand)) {
121
+ return false;
122
+ }
123
+ break;
124
+ default:
125
+ // Unknown operator — fail closed to avoid leaking data
126
+ return false;
127
+ }
128
+ }
129
+
130
+ return true;
131
+ };
132
+
133
+ /**
134
+ * Check if a document matches a MongoDB-style query in memory.
135
+ *
136
+ * @param doc - The document to test (plain object or Mongoose document)
137
+ * @param query - MongoDB-style query object
138
+ * @returns true if the document matches all query conditions
139
+ */
140
+ // biome-ignore lint/suspicious/noExplicitAny: doc is arbitrary; query values are arbitrary user-supplied JSON
141
+ export const matchesQuery = (doc: any, query: Record<string, any>): boolean => {
142
+ for (const [key, condition] of Object.entries(query)) {
143
+ if (key === "$and") {
144
+ if (!Array.isArray(condition)) {
145
+ return false;
146
+ }
147
+ for (const subQuery of condition) {
148
+ if (!matchesQuery(doc, subQuery)) {
149
+ return false;
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+
155
+ if (key === "$or") {
156
+ if (!Array.isArray(condition)) {
157
+ return false;
158
+ }
159
+ let matched = false;
160
+ for (const subQuery of condition) {
161
+ if (matchesQuery(doc, subQuery)) {
162
+ matched = true;
163
+ break;
164
+ }
165
+ }
166
+ if (!matched) {
167
+ return false;
168
+ }
169
+ continue;
170
+ }
171
+
172
+ const value = getNestedValue(doc, key);
173
+ if (!matchesCondition(value, condition)) {
174
+ return false;
175
+ }
176
+ }
177
+
178
+ return true;
179
+ };
@@ -0,0 +1,132 @@
1
+ // biome-ignore-all lint/suspicious/noExplicitAny: query filters are dynamic MongoDB query objects
2
+ /**
3
+ * Manages query subscriptions for Socket.io clients.
4
+ *
5
+ * When a client subscribes to a query (e.g., `{completed: false}` on the "todos" collection),
6
+ * the query is stored here. The change stream watcher consults this store to determine
7
+ * which query rooms should receive a given change event.
8
+ */
9
+
10
+ interface QuerySubscription {
11
+ collection: string;
12
+ // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
13
+ query: Record<string, any>;
14
+ queryId: string;
15
+ }
16
+
17
+ /**
18
+ * Compute a deterministic queryId from collection and query on the server side.
19
+ * This prevents clients from hijacking other subscriptions by providing a colliding queryId.
20
+ */
21
+ export const computeQueryId = (
22
+ collection: string,
23
+ // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
24
+ query: Record<string, any>
25
+ ): string => {
26
+ const sortedKeys = Object.keys(query).sort();
27
+ // biome-ignore lint/suspicious/noExplicitAny: mirrors the input query value shape
28
+ const normalized: Record<string, any> = {};
29
+ for (const key of sortedKeys) {
30
+ normalized[key] = query[key];
31
+ }
32
+ return `${collection}:${JSON.stringify(normalized)}`;
33
+ };
34
+
35
+ /** queryId → query subscription details (shared across all sockets in that room) */
36
+ const querySubscriptions = new Map<string, QuerySubscription>();
37
+
38
+ /** socketId → set of queryIds that socket is subscribed to */
39
+ const socketQueries = new Map<string, Set<string>>();
40
+
41
+ /**
42
+ * Register a query subscription for a socket.
43
+ * The socket joins the `query:{queryId}` room (handled by the caller).
44
+ */
45
+ export const addQuerySubscription = (
46
+ socketId: string,
47
+ collection: string,
48
+ // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
49
+ query: Record<string, any>,
50
+ queryId: string
51
+ ): void => {
52
+ querySubscriptions.set(queryId, {collection, query, queryId});
53
+
54
+ if (!socketQueries.has(socketId)) {
55
+ socketQueries.set(socketId, new Set());
56
+ }
57
+ socketQueries.get(socketId)?.add(queryId);
58
+ };
59
+
60
+ /**
61
+ * Remove a single query subscription for a socket.
62
+ */
63
+ export const removeQuerySubscription = (socketId: string, queryId: string): void => {
64
+ socketQueries.get(socketId)?.delete(queryId);
65
+
66
+ // Check if any other socket still has this query
67
+ let stillUsed = false;
68
+ for (const [, queryIds] of socketQueries) {
69
+ if (queryIds.has(queryId)) {
70
+ stillUsed = true;
71
+ break;
72
+ }
73
+ }
74
+
75
+ if (!stillUsed) {
76
+ querySubscriptions.delete(queryId);
77
+ }
78
+ };
79
+
80
+ /**
81
+ * Remove all query subscriptions for a disconnected socket.
82
+ */
83
+ export const removeAllSocketQueries = (socketId: string): void => {
84
+ const queryIds = socketQueries.get(socketId);
85
+ if (!queryIds) {
86
+ return;
87
+ }
88
+
89
+ socketQueries.delete(socketId);
90
+
91
+ // Clean up any queries that no longer have subscribers
92
+ for (const queryId of queryIds) {
93
+ let stillUsed = false;
94
+ for (const [, otherQueryIds] of socketQueries) {
95
+ if (otherQueryIds.has(queryId)) {
96
+ stillUsed = true;
97
+ break;
98
+ }
99
+ }
100
+ if (!stillUsed) {
101
+ querySubscriptions.delete(queryId);
102
+ }
103
+ }
104
+ };
105
+
106
+ /**
107
+ * Get all unique query subscriptions for a given collection.
108
+ * Used by the change stream watcher to evaluate which query rooms to emit to.
109
+ */
110
+ export const getQuerySubscriptionsForCollection = (
111
+ collection: string
112
+ // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
113
+ ): {queryId: string; query: Record<string, any>}[] => {
114
+ // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
115
+ const result: {queryId: string; query: Record<string, any>}[] = [];
116
+
117
+ for (const [queryId, sub] of querySubscriptions) {
118
+ if (sub.collection === collection) {
119
+ result.push({query: sub.query, queryId});
120
+ }
121
+ }
122
+
123
+ return result;
124
+ };
125
+
126
+ /**
127
+ * Clear all subscriptions (for testing).
128
+ */
129
+ export const clearQueryStore = (): void => {
130
+ querySubscriptions.clear();
131
+ socketQueries.clear();
132
+ };