@terreno/api 0.18.0 → 0.19.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.
@@ -265,6 +265,31 @@ describe("getOpenApiSpecForModel edge cases", () => {
265
265
  });
266
266
  });
267
267
 
268
+ describe("getOpenApiSpecForModel populate with existing properties", () => {
269
+ it("merges populated properties into a path that already has properties", () => {
270
+ const result = getOpenApiSpecForModel(FoodModel, {
271
+ populatePaths: [{path: "likesIds.userId"}],
272
+ });
273
+ // likesIds is an array subschema with its own properties already;
274
+ // populating userId should merge the user properties into the existing structure.
275
+ expect(result.properties.likesIds).toBeDefined();
276
+ const likesIds = result.properties.likesIds as Record<string, unknown>;
277
+ const items = likesIds.items as Record<string, Record<string, unknown>>;
278
+ expect(items.properties.userId).toBeDefined();
279
+ });
280
+
281
+ it("creates intermediate path structure when navigating to nested populate", () => {
282
+ // eatenBy is defined as [{ ref: "User", type: ObjectId }] - an array of refs.
283
+ // When we populate eatenBy, the openApiPath resolves through items.
284
+ const result = getOpenApiSpecForModel(FoodModel, {
285
+ populatePaths: [{path: "eatenBy"}],
286
+ });
287
+ expect(result.properties.eatenBy).toBeDefined();
288
+ const eatenBy = result.properties.eatenBy as Record<string, unknown>;
289
+ expect(eatenBy.items).toBeDefined();
290
+ });
291
+ });
292
+
268
293
  describe("filterKeys (via getOpenApiSpecForModel populatePaths)", () => {
269
294
  it("filters populated fields using dot-notation keys", () => {
270
295
  const result = getOpenApiSpecForModel(FoodModel, {
@@ -6,7 +6,6 @@
6
6
  * Supports: equality, $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists, $and, $or, $not.
7
7
  */
8
8
 
9
- // biome-ignore lint/suspicious/noExplicitAny: traversing arbitrary nested document fields by user-supplied dotted path
10
9
  const getNestedValue = (doc: any, path: string): any => {
11
10
  const parts = path.split(".");
12
11
  let current = doc;
@@ -19,7 +18,6 @@ const getNestedValue = (doc: any, path: string): any => {
19
18
  return current;
20
19
  };
21
20
 
22
- // biome-ignore lint/suspicious/noExplicitAny: value may be any document field type (string, number, ObjectId, etc.)
23
21
  const normalize = (value: any): any => {
24
22
  if (value === null || value === undefined) {
25
23
  return value;
@@ -36,7 +34,6 @@ const normalize = (value: any): any => {
36
34
  return value;
37
35
  };
38
36
 
39
- // biome-ignore lint/suspicious/noExplicitAny: rawValue is an arbitrary document field, condition is an arbitrary user query operand
40
37
  const matchesCondition = (rawValue: any, condition: any): boolean => {
41
38
  const value = normalize(rawValue);
42
39
 
@@ -91,7 +88,6 @@ const matchesCondition = (rawValue: any, condition: any): boolean => {
91
88
  return false;
92
89
  }
93
90
  const inValues = operand.map(normalize);
94
- // biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
95
91
  if (!inValues.some((v: any) => v === value || String(v) === String(value))) {
96
92
  return false;
97
93
  }
@@ -102,7 +98,6 @@ const matchesCondition = (rawValue: any, condition: any): boolean => {
102
98
  return false;
103
99
  }
104
100
  const ninValues = operand.map(normalize);
105
- // biome-ignore lint/suspicious/noExplicitAny: normalized value of arbitrary document field
106
101
  if (ninValues.some((v: any) => v === value || String(v) === String(value))) {
107
102
  return false;
108
103
  }
@@ -137,7 +132,6 @@ const matchesCondition = (rawValue: any, condition: any): boolean => {
137
132
  * @param query - MongoDB-style query object
138
133
  * @returns true if the document matches all query conditions
139
134
  */
140
- // biome-ignore lint/suspicious/noExplicitAny: doc is arbitrary; query values are arbitrary user-supplied JSON
141
135
  export const matchesQuery = (doc: any, query: Record<string, any>): boolean => {
142
136
  for (const [key, condition] of Object.entries(query)) {
143
137
  if (key === "$and") {
@@ -9,7 +9,6 @@
9
9
 
10
10
  interface QuerySubscription {
11
11
  collection: string;
12
- // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
13
12
  query: Record<string, any>;
14
13
  queryId: string;
15
14
  }
@@ -18,13 +17,8 @@ interface QuerySubscription {
18
17
  * Compute a deterministic queryId from collection and query on the server side.
19
18
  * This prevents clients from hijacking other subscriptions by providing a colliding queryId.
20
19
  */
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 => {
20
+ export const computeQueryId = (collection: string, query: Record<string, any>): string => {
26
21
  const sortedKeys = Object.keys(query).sort();
27
- // biome-ignore lint/suspicious/noExplicitAny: mirrors the input query value shape
28
22
  const normalized: Record<string, any> = {};
29
23
  for (const key of sortedKeys) {
30
24
  normalized[key] = query[key];
@@ -45,7 +39,6 @@ const socketQueries = new Map<string, Set<string>>();
45
39
  export const addQuerySubscription = (
46
40
  socketId: string,
47
41
  collection: string,
48
- // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
49
42
  query: Record<string, any>,
50
43
  queryId: string
51
44
  ): void => {
@@ -109,9 +102,7 @@ export const removeAllSocketQueries = (socketId: string): void => {
109
102
  */
110
103
  export const getQuerySubscriptionsForCollection = (
111
104
  collection: string
112
- // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
113
105
  ): {queryId: string; query: Record<string, any>}[] => {
114
- // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
115
106
  const result: {queryId: string; query: Record<string, any>}[] = [];
116
107
 
117
108
  for (const [queryId, sub] of querySubscriptions) {
@@ -1776,6 +1776,17 @@ describe("startChangeStreamWatcher", () => {
1776
1776
  };
1777
1777
  };
1778
1778
 
1779
+ const invokeRegisteredChangeHandler = async (
1780
+ mockStream: ReturnType<typeof createMockChangeStream>,
1781
+ event: Record<string, unknown>
1782
+ ): Promise<void> => {
1783
+ const changeHandler = mockStream.listeners.get("change");
1784
+ if (!changeHandler) {
1785
+ throw new Error("expected change handler");
1786
+ }
1787
+ await changeHandler(event);
1788
+ };
1789
+
1779
1790
  const createMockIo = () => {
1780
1791
  const rooms = new Map<string, Set<string>>();
1781
1792
  const sockets = new Map<string, any>();
@@ -1864,9 +1875,7 @@ describe("startChangeStreamWatcher", () => {
1864
1875
  startChangeStreamWatcher(io, {}, true);
1865
1876
 
1866
1877
  // Trigger an insert change event
1867
- const changeHandler = mockStream.listeners.get("change");
1868
- expect(changeHandler).toBeDefined();
1869
- await changeHandler!({
1878
+ await invokeRegisteredChangeHandler(mockStream, {
1870
1879
  documentKey: {_id: "doc-1"},
1871
1880
  fullDocument: {_id: "doc-1", name: "Test Todo"},
1872
1881
  ns: {coll: "todos"},
@@ -1886,9 +1895,8 @@ describe("startChangeStreamWatcher", () => {
1886
1895
 
1887
1896
  startChangeStreamWatcher(io, {}, true);
1888
1897
 
1889
- const changeHandler = mockStream.listeners.get("change");
1890
1898
  // Trigger for an unregistered collection — should not throw
1891
- await changeHandler!({
1899
+ await invokeRegisteredChangeHandler(mockStream, {
1892
1900
  documentKey: {_id: "doc-1"},
1893
1901
  fullDocument: {_id: "doc-1"},
1894
1902
  ns: {coll: "unknown_collection"},
@@ -1927,9 +1935,8 @@ describe("startChangeStreamWatcher", () => {
1927
1935
 
1928
1936
  startChangeStreamWatcher(io, {}, true);
1929
1937
 
1930
- const changeHandler = mockStream.listeners.get("change");
1931
1938
  // Update event should be skipped because "update" not in methods
1932
- await changeHandler!({
1939
+ await invokeRegisteredChangeHandler(mockStream, {
1933
1940
  documentKey: {_id: "doc-1"},
1934
1941
  fullDocument: {_id: "doc-1", name: "Updated"},
1935
1942
  ns: {coll: "todos"},
@@ -1969,9 +1976,8 @@ describe("startChangeStreamWatcher", () => {
1969
1976
 
1970
1977
  startChangeStreamWatcher(io, {}, true);
1971
1978
 
1972
- const changeHandler = mockStream.listeners.get("change");
1973
1979
  // Hard delete (no fullDocument)
1974
- await changeHandler!({
1980
+ await invokeRegisteredChangeHandler(mockStream, {
1975
1981
  documentKey: {_id: "doc-1"},
1976
1982
  ns: {coll: "todos"},
1977
1983
  operationType: "delete",
@@ -2009,9 +2015,8 @@ describe("startChangeStreamWatcher", () => {
2009
2015
 
2010
2016
  startChangeStreamWatcher(io, {}, true);
2011
2017
 
2012
- const changeHandler = mockStream.listeners.get("change");
2013
2018
  // Hard delete for broadcast strategy
2014
- await changeHandler!({
2019
+ await invokeRegisteredChangeHandler(mockStream, {
2015
2020
  documentKey: {_id: "doc-1"},
2016
2021
  ns: {coll: "broadcasts"},
2017
2022
  operationType: "delete",
@@ -2049,8 +2054,7 @@ describe("startChangeStreamWatcher", () => {
2049
2054
 
2050
2055
  startChangeStreamWatcher(io, {}, true);
2051
2056
 
2052
- const changeHandler = mockStream.listeners.get("change");
2053
- await changeHandler!({
2057
+ await invokeRegisteredChangeHandler(mockStream, {
2054
2058
  documentKey: {_id: "doc-1"},
2055
2059
  fullDocument: {_id: "doc-1", name: "Updated", status: "done"},
2056
2060
  ns: {coll: "todos"},
@@ -2110,9 +2114,8 @@ describe("startChangeStreamWatcher", () => {
2110
2114
 
2111
2115
  startChangeStreamWatcher(io, {ignoredOperations: ["insert"]}, true);
2112
2116
 
2113
- const changeHandler = mockStream.listeners.get("change");
2114
2117
  // This insert should be skipped because "insert" is ignored
2115
- await changeHandler!({
2118
+ await invokeRegisteredChangeHandler(mockStream, {
2116
2119
  documentKey: {_id: "doc-1"},
2117
2120
  fullDocument: {_id: "doc-1"},
2118
2121
  ns: {coll: "todos"},
@@ -2132,15 +2135,14 @@ describe("startChangeStreamWatcher", () => {
2132
2135
 
2133
2136
  startChangeStreamWatcher(io, {}, true);
2134
2137
 
2135
- const changeHandler = mockStream.listeners.get("change");
2136
2138
  // Missing ns.coll
2137
- await changeHandler!({
2139
+ await invokeRegisteredChangeHandler(mockStream, {
2138
2140
  documentKey: {_id: "doc-1"},
2139
2141
  ns: {},
2140
2142
  operationType: "insert",
2141
2143
  });
2142
2144
  // Missing documentKey
2143
- await changeHandler!({
2145
+ await invokeRegisteredChangeHandler(mockStream, {
2144
2146
  documentKey: {},
2145
2147
  ns: {coll: "todos"},
2146
2148
  operationType: "insert",
@@ -2159,9 +2161,8 @@ describe("startChangeStreamWatcher", () => {
2159
2161
 
2160
2162
  startChangeStreamWatcher(io, {}, true);
2161
2163
 
2162
- const changeHandler = mockStream.listeners.get("change");
2163
2164
  // "drop" is not in our pipeline filter, should be skipped
2164
- await changeHandler!({
2165
+ await invokeRegisteredChangeHandler(mockStream, {
2165
2166
  operationType: "drop",
2166
2167
  });
2167
2168
  });
@@ -2257,9 +2258,8 @@ describe("startChangeStreamWatcher", () => {
2257
2258
 
2258
2259
  startChangeStreamWatcher(io, {}, true);
2259
2260
 
2260
- const changeHandler = mockStream.listeners.get("change");
2261
2261
  // Should not throw even though permission check throws
2262
- await changeHandler!({
2262
+ await invokeRegisteredChangeHandler(mockStream, {
2263
2263
  documentKey: {_id: "doc-1"},
2264
2264
  fullDocument: {_id: "doc-1", name: "Test"},
2265
2265
  ns: {coll: "todos"},
@@ -2317,7 +2317,7 @@ describe("RealtimeApp.onServerCreated", () => {
2317
2317
  });
2318
2318
 
2319
2319
  const makeServer = (): import("http").Server => {
2320
- const http = require("http");
2320
+ const http = require("node:http");
2321
2321
  const server = http.createServer();
2322
2322
  servers.push(server);
2323
2323
  return server;
@@ -2366,7 +2366,7 @@ describe("RealtimeApp.setupAdapter (private, via onServerCreated config)", () =>
2366
2366
  });
2367
2367
 
2368
2368
  const makeServer = (): import("http").Server => {
2369
- const http = require("http");
2369
+ const http = require("node:http");
2370
2370
  const server = http.createServer();
2371
2371
  servers.push(server);
2372
2372
  return server;
@@ -60,7 +60,6 @@ export interface RealtimeSocketLike extends SocketWithDecodedToken {
60
60
  join: (room: string) => Promise<void> | void;
61
61
  leave: (room: string) => Promise<void> | void;
62
62
  emit: (event: string, payload: unknown) => void;
63
- // biome-ignore lint/suspicious/noExplicitAny: Socket.io event handlers accept arbitrary argument shapes per event name
64
63
  on: (event: string, handler: (...args: any[]) => any) => void;
65
64
  }
66
65
 
@@ -17,7 +17,6 @@ export interface RealtimeRegistryEntry {
17
17
  /**
18
18
  * Full modelRouter options (for responseHandler, permissions, etc.).
19
19
  */
20
- // biome-ignore lint/suspicious/noExplicitAny: registry stores heterogeneous models — narrowing the generic is not useful at the registry level
21
20
  options: ModelRouterOptions<any>;
22
21
  }
23
22
 
@@ -19,10 +19,8 @@ export interface RealtimeConfig {
19
19
  | "owner"
20
20
  | "model"
21
21
  | "broadcast"
22
- // biome-ignore lint/suspicious/noExplicitAny: doc is an arbitrary Mongoose document; consumers cast to their model type
23
22
  | ((doc: any, method: string, req: express.Request) => string[]);
24
23
  /** Custom serializer for real-time events. Falls back to the modelRouter responseHandler. */
25
- // biome-ignore lint/suspicious/noExplicitAny: doc shape and return value are consumer-defined per model
26
24
  realtimeResponseHandler?: (doc: any, method: string) => any;
27
25
  }
28
26
 
@@ -39,7 +37,6 @@ export interface RealtimeEvent {
39
37
  /** Document ID */
40
38
  id: string;
41
39
  /** Serialized document data (omitted for hard deletes) */
42
- // biome-ignore lint/suspicious/noExplicitAny: emitted document shape varies by model and serializer
43
40
  data?: any;
44
41
  /** Fields that were updated (for update events from change streams) */
45
42
  updatedFields?: string[];
@@ -105,7 +102,6 @@ export interface QuerySubscription {
105
102
  /** Collection tag (e.g. "todos") */
106
103
  collection: string;
107
104
  /** MongoDB-style query filter (e.g. {completed: false}) */
108
- // biome-ignore lint/suspicious/noExplicitAny: MongoDB query filter values are arbitrary user-supplied JSON
109
105
  query: Record<string, any>;
110
106
  /** Client-provided queryId (ignored — server computes a canonical ID) */
111
107
  queryId?: string;