@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.
- package/dist/__tests__/versionCheckPlugin.test.js +53 -3
- package/dist/api.arrayOperations.test.js +1 -0
- package/dist/api.asyncHandler.test.d.ts +1 -0
- package/dist/api.asyncHandler.test.js +236 -0
- package/dist/api.d.ts +15 -4
- package/dist/api.errors.test.js +1 -0
- package/dist/api.hooks.test.js +1 -0
- package/dist/api.js +153 -104
- package/dist/api.query.test.js +1 -0
- package/dist/api.test.js +174 -0
- package/dist/auth.d.ts +10 -5
- package/dist/auth.js +163 -90
- package/dist/auth.test.js +159 -0
- package/dist/betterAuthApp.test.js +1 -0
- package/dist/betterAuthSetup.d.ts +5 -6
- package/dist/betterAuthSetup.js +17 -14
- package/dist/betterAuthSetup.test.js +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +248 -0
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +328 -0
- package/dist/configuration.test.js +1 -0
- package/dist/configurationApp.d.ts +1 -1
- package/dist/configurationApp.js +17 -13
- package/dist/configurationPlugin.test.js +1 -0
- package/dist/consentApp.test.js +1 -0
- package/dist/envConfigurationPlugin.d.ts +2 -0
- package/dist/envConfigurationPlugin.js +173 -0
- package/dist/envConfigurationPlugin.test.d.ts +1 -0
- package/dist/envConfigurationPlugin.test.js +322 -0
- package/dist/errors.d.ts +18 -7
- package/dist/errors.js +106 -10
- package/dist/errors.test.js +16 -1
- package/dist/example.js +16 -7
- package/dist/expressServer.d.ts +10 -9
- package/dist/expressServer.js +62 -53
- package/dist/expressServer.test.js +53 -2
- package/dist/githubAuth.d.ts +2 -1
- package/dist/githubAuth.js +41 -26
- package/dist/githubAuth.test.js +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/logger.d.ts +1 -1
- package/dist/logger.js +42 -20
- package/dist/models/versionConfig.d.ts +2 -0
- package/dist/models/versionConfig.js +8 -0
- package/dist/notifiers/googleChatNotifier.js +14 -16
- package/dist/notifiers/googleChatNotifier.test.js +1 -0
- package/dist/notifiers/slackNotifier.js +16 -14
- package/dist/notifiers/slackNotifier.test.js +41 -3
- package/dist/notifiers/zoomNotifier.js +7 -10
- package/dist/notifiers/zoomNotifier.test.js +1 -0
- package/dist/openApi.d.ts +1 -1
- package/dist/openApi.test.js +1 -0
- package/dist/openApiBuilder.d.ts +39 -6
- package/dist/openApiBuilder.js +1 -31
- package/dist/openApiBuilder.test.js +1 -0
- package/dist/openApiValidator.js +1 -0
- package/dist/openApiValidator.test.js +65 -0
- package/dist/permissions.d.ts +4 -4
- package/dist/permissions.js +67 -65
- package/dist/permissions.middleware.test.js +1 -0
- package/dist/permissions.test.js +1 -0
- package/dist/plugins.d.ts +5 -5
- package/dist/plugins.js +18 -9
- package/dist/plugins.test.js +1 -1
- package/dist/populate.d.ts +15 -8
- package/dist/populate.js +23 -24
- package/dist/populate.test.js +1 -0
- package/dist/realtime/changeStreamWatcher.d.ts +73 -0
- package/dist/realtime/changeStreamWatcher.js +720 -0
- package/dist/realtime/index.d.ts +6 -0
- package/dist/realtime/index.js +27 -0
- package/dist/realtime/queryMatcher.d.ts +14 -0
- package/dist/realtime/queryMatcher.js +250 -0
- package/dist/realtime/queryStore.d.ts +37 -0
- package/dist/realtime/queryStore.js +195 -0
- package/dist/realtime/realtime.test.d.ts +10 -0
- package/dist/realtime/realtime.test.js +2158 -0
- package/dist/realtime/realtimeApp.d.ts +93 -0
- package/dist/realtime/realtimeApp.js +560 -0
- package/dist/realtime/registry.d.ts +40 -0
- package/dist/realtime/registry.js +38 -0
- package/dist/realtime/socketUser.d.ts +10 -0
- package/dist/realtime/socketUser.js +17 -0
- package/dist/realtime/types.d.ts +100 -0
- package/dist/realtime/types.js +2 -0
- package/dist/requestContext.d.ts +37 -0
- package/dist/requestContext.js +344 -0
- package/dist/requestContext.test.d.ts +1 -0
- package/dist/requestContext.test.js +241 -0
- package/dist/terrenoApp.d.ts +8 -0
- package/dist/terrenoApp.js +50 -13
- package/dist/terrenoApp.test.js +194 -21
- package/dist/terrenoPlugin.d.ts +11 -0
- package/dist/tests/bunSetup.js +1 -0
- package/dist/tests.js +1 -1
- package/dist/transformers.d.ts +2 -2
- package/dist/transformers.js +5 -3
- package/dist/transformers.test.js +90 -0
- package/dist/types/consentResponse.d.ts +6 -3
- package/dist/versionCheckPlugin.d.ts +2 -0
- package/dist/versionCheckPlugin.js +18 -12
- package/package.json +4 -2
- package/src/__tests__/versionCheckPlugin.test.ts +37 -3
- package/src/api.arrayOperations.test.ts +1 -0
- package/src/api.asyncHandler.test.ts +177 -0
- package/src/api.errors.test.ts +1 -0
- package/src/api.hooks.test.ts +1 -0
- package/src/api.query.test.ts +1 -0
- package/src/api.test.ts +132 -0
- package/src/api.ts +199 -84
- package/src/auth.test.ts +160 -0
- package/src/auth.ts +120 -50
- package/src/betterAuthApp.test.ts +1 -0
- package/src/betterAuthSetup.test.ts +1 -0
- package/src/betterAuthSetup.ts +46 -19
- package/src/config.test.ts +255 -0
- package/src/config.ts +206 -0
- package/src/configuration.test.ts +1 -0
- package/src/configurationApp.ts +59 -24
- package/src/configurationPlugin.test.ts +1 -0
- package/src/consentApp.test.ts +1 -0
- package/src/envConfigurationPlugin.test.ts +143 -0
- package/src/envConfigurationPlugin.ts +100 -0
- package/src/errors.test.ts +19 -1
- package/src/errors.ts +94 -20
- package/src/example.ts +46 -21
- package/src/express.d.ts +18 -1
- package/src/expressServer.test.ts +50 -2
- package/src/expressServer.ts +80 -50
- package/src/githubAuth.test.ts +1 -0
- package/src/githubAuth.ts +59 -38
- package/src/index.ts +4 -0
- package/src/logger.ts +47 -17
- package/src/models/versionConfig.ts +13 -2
- package/src/notifiers/googleChatNotifier.test.ts +1 -0
- package/src/notifiers/googleChatNotifier.ts +7 -9
- package/src/notifiers/slackNotifier.test.ts +29 -3
- package/src/notifiers/slackNotifier.ts +9 -7
- package/src/notifiers/zoomNotifier.test.ts +1 -0
- package/src/notifiers/zoomNotifier.ts +8 -11
- package/src/openApi.test.ts +1 -0
- package/src/openApi.ts +4 -4
- package/src/openApiBuilder.test.ts +1 -0
- package/src/openApiBuilder.ts +14 -11
- package/src/openApiValidator.test.ts +59 -0
- package/src/openApiValidator.ts +3 -2
- package/src/permissions.middleware.test.ts +1 -0
- package/src/permissions.test.ts +1 -0
- package/src/permissions.ts +30 -25
- package/src/plugins.test.ts +1 -1
- package/src/plugins.ts +21 -14
- package/src/populate.test.ts +1 -0
- package/src/populate.ts +44 -36
- package/src/realtime/changeStreamWatcher.ts +568 -0
- package/src/realtime/index.ts +34 -0
- package/src/realtime/queryMatcher.ts +179 -0
- package/src/realtime/queryStore.ts +132 -0
- package/src/realtime/realtime.test.ts +1755 -0
- package/src/realtime/realtimeApp.ts +478 -0
- package/src/realtime/registry.ts +64 -0
- package/src/realtime/socketUser.ts +25 -0
- package/src/realtime/types.ts +112 -0
- package/src/requestContext.test.ts +196 -0
- package/src/requestContext.ts +368 -0
- package/src/terrenoApp.test.ts +137 -11
- package/src/terrenoApp.ts +64 -17
- package/src/terrenoPlugin.ts +12 -0
- package/src/tests/bunSetup.ts +1 -0
- package/src/tests.ts +7 -2
- package/src/transformers.test.ts +70 -2
- package/src/transformers.ts +15 -7
- package/src/types/consentResponse.ts +8 -10
- 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
|
+
};
|