@flowerforce/flowerbase-client 0.1.1-beta.8 → 0.1.1-beta.9

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/README.md CHANGED
@@ -125,7 +125,7 @@ Metodi disponibili su `collection`:
125
125
  - `updateOne(filter, update, options?)`
126
126
  - `updateMany(filter, update, options?)`
127
127
  - `deleteOne(filter, options?)`
128
- - `watch(pipeline?, options?)`
128
+ - `watch(options?)`
129
129
 
130
130
  ## Watch / Change streams
131
131
 
@@ -143,6 +143,17 @@ try {
143
143
  }
144
144
  ```
145
145
 
146
+ Esempio con filtro Realm-like:
147
+
148
+ ```ts
149
+ const stream = collection.watch({
150
+ filter: {
151
+ operationType: 'update',
152
+ 'fullDocument.type': 'perennial'
153
+ }
154
+ })
155
+ ```
156
+
146
157
  ## BSON / EJSON
147
158
 
148
159
  Il client esporta anche:
@@ -1 +1 @@
1
- {"version":3,"file":"mongo.d.ts","sourceRoot":"","sources":["../src/mongo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAA;AAoBzD,eAAO,MAAM,iBAAiB,QAAS,GAAG,eAAe,MAAM,UAAU,MAAM,KAAG,eAoFhF,CAAA"}
1
+ {"version":3,"file":"mongo.d.ts","sourceRoot":"","sources":["../src/mongo.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAA;AAEhC,OAAO,EAAkB,eAAe,EAAE,MAAM,SAAS,CAAA;AAoBzD,eAAO,MAAM,iBAAiB,QAAS,GAAG,eAAe,MAAM,UAAU,MAAM,KAAG,eAyFhF,CAAA"}
package/dist/mongo.js CHANGED
@@ -35,37 +35,41 @@ const createMongoClient = (app, serviceName, userId) => ({
35
35
  return mapResult(result);
36
36
  };
37
37
  const normalizeWatchInput = (input) => {
38
+ if (typeof input === 'undefined') {
39
+ return { filter: undefined, ids: undefined };
40
+ }
38
41
  if (Array.isArray(input)) {
39
- return { pipeline: input, options: {} };
42
+ throw new Error('watch accepts only an options object with "filter" or "ids"');
43
+ }
44
+ if (!input || typeof input !== 'object') {
45
+ throw new Error('watch options must be an object');
46
+ }
47
+ const typed = input;
48
+ const keys = Object.keys(typed);
49
+ const hasOnlyAllowedKeys = keys.every((key) => key === 'ids' || key === 'filter');
50
+ if (!hasOnlyAllowedKeys) {
51
+ throw new Error('watch options support only "filter" or "ids"');
40
52
  }
41
- if (input && typeof input === 'object' && ('ids' in input || 'filter' in input)) {
42
- const typed = input;
53
+ if (typed.ids || typed.filter) {
43
54
  if (typed.ids && typed.filter) {
44
55
  throw new Error('watch options cannot include both "ids" and "filter"');
45
56
  }
46
- const { ids, filter, ...options } = typed;
57
+ const { ids, filter } = typed;
58
+ if (filter && typeof filter === 'object' && '$match' in filter) {
59
+ throw new Error('watch filter must be a query object, not a $match stage');
60
+ }
47
61
  if (ids) {
48
- return {
49
- pipeline: [{ $match: { 'documentKey._id': { $in: ids } } }],
50
- options
51
- };
62
+ if (!Array.isArray(ids)) {
63
+ throw new Error('watch ids must be an array');
64
+ }
65
+ return { filter: undefined, ids };
52
66
  }
53
67
  if (filter) {
54
- return {
55
- pipeline: [{ $match: filter }],
56
- options
57
- };
68
+ return { filter, ids: undefined };
58
69
  }
59
- return { pipeline: [], options };
60
- }
61
- if (input && typeof input === 'object' && ('pipeline' in input || 'options' in input)) {
62
- const typed = input;
63
- return {
64
- pipeline: typed.pipeline ?? [],
65
- options: typed.options ?? {}
66
- };
70
+ return { filter: undefined, ids: undefined };
67
71
  }
68
- return { pipeline: [], options: input ?? {} };
72
+ throw new Error('watch options must include "filter" or "ids"');
69
73
  };
70
74
  return {
71
75
  find: (query = {}, options = {}) => callService('find', [{ query, options }]),
@@ -81,8 +85,8 @@ const createMongoClient = (app, serviceName, userId) => ({
81
85
  updateMany: (filter, update, options = {}) => callService('updateMany', [{ filter, update, options }]),
82
86
  deleteOne: (filter, options = {}) => callService('deleteOne', [{ query: filter, options }]),
83
87
  deleteMany: (filter, options = {}) => callService('deleteMany', [{ query: filter, options }]),
84
- watch: (input) => {
85
- const { pipeline, options } = normalizeWatchInput(input);
88
+ watch: (options) => {
89
+ const { filter, ids } = normalizeWatchInput(options);
86
90
  const session = app.getSessionOrThrow(userId);
87
91
  return (0, watch_1.createWatchIterator)({
88
92
  appId: app.id,
@@ -90,8 +94,8 @@ const createMongoClient = (app, serviceName, userId) => ({
90
94
  accessToken: session.accessToken,
91
95
  database,
92
96
  collection,
93
- pipeline,
94
- options,
97
+ filter,
98
+ ids,
95
99
  timeout: app.timeout
96
100
  });
97
101
  }
package/dist/types.d.ts CHANGED
@@ -39,8 +39,8 @@ export type WatchConfig = {
39
39
  accessToken: string;
40
40
  database: string;
41
41
  collection: string;
42
- pipeline?: unknown[];
43
- options?: Record<string, unknown>;
42
+ filter?: Record<string, unknown>;
43
+ ids?: unknown[];
44
44
  timeout?: number;
45
45
  };
46
46
  export type WatchAsyncIterator<TChange = unknown> = AsyncIterableIterator<TChange> & {
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAoCzD,eAAO,MAAM,mBAAmB,WAAY,WAAW,KAAG,mBAAmB,OAAO,CAiHnF,CAAA"}
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../src/watch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAyCzD,eAAO,MAAM,mBAAmB,WAAY,WAAW,KAAG,mBAAmB,OAAO,CAiHnF,CAAA"}
package/dist/watch.js CHANGED
@@ -1,16 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createWatchIterator = void 0;
4
+ const bson_1 = require("./bson");
4
5
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5
- const createWatchRequest = ({ database, collection, pipeline = [], options = {} }) => ({
6
+ const createWatchRequest = ({ database, collection, filter, ids }) => ({
6
7
  name: 'watch',
7
8
  service: 'mongodb-atlas',
8
9
  arguments: [
9
10
  {
10
11
  database,
11
12
  collection,
12
- pipeline,
13
- options
13
+ ...(filter ? { filter } : {}),
14
+ ...(ids ? { ids } : {})
14
15
  }
15
16
  ]
16
17
  });
@@ -27,7 +28,7 @@ const parseSsePayload = (line) => {
27
28
  if (!raw)
28
29
  return null;
29
30
  try {
30
- return JSON.parse(raw);
31
+ return bson_1.EJSON.deserialize(JSON.parse(raw));
31
32
  }
32
33
  catch {
33
34
  return raw;
@@ -62,7 +63,7 @@ const createWatchIterator = (config) => {
62
63
  const controller = new AbortController();
63
64
  activeController = controller;
64
65
  const request = createWatchRequest(config);
65
- const encoded = toBase64(JSON.stringify(request));
66
+ const encoded = toBase64(JSON.stringify(bson_1.EJSON.serialize(request, { relaxed: false })));
66
67
  const url = `${config.baseUrl}/api/client/v2.0/app/${config.appId}/functions/call?baas_request=${encodeURIComponent(encoded)}`;
67
68
  try {
68
69
  const response = await fetch(url, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flowerforce/flowerbase-client",
3
- "version": "0.1.1-beta.8",
3
+ "version": "0.1.1-beta.9",
4
4
  "description": "Client for Flowerbase",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -1,4 +1,5 @@
1
1
  import { App } from '../app'
2
+ import { EJSON, ObjectId } from '../bson'
2
3
  import { Credentials } from '../credentials'
3
4
 
4
5
  const streamFromLines = (lines: string[]) => {
@@ -19,8 +20,11 @@ const decodeBaasRequest = (url: string) => {
19
20
  if (!encoded) throw new Error('baas_request missing')
20
21
  const base64 = decodeURIComponent(encoded)
21
22
  const json = Buffer.from(base64, 'base64').toString('utf8')
22
- return JSON.parse(json) as {
23
- arguments: Array<{ pipeline?: unknown[] }>
23
+ return EJSON.deserialize(JSON.parse(json)) as {
24
+ arguments: Array<{
25
+ filter?: Record<string, unknown>
26
+ ids?: unknown[]
27
+ }>
24
28
  }
25
29
  }
26
30
 
@@ -147,7 +151,7 @@ describe('flowerbase-client watch', () => {
147
151
  iterator.close()
148
152
  })
149
153
 
150
- it('maps watch ids/filter options into pipeline', async () => {
154
+ it('maps watch ids/filter options into watch arguments', async () => {
151
155
  global.fetch = jest
152
156
  .fn()
153
157
  .mockResolvedValueOnce({
@@ -175,14 +179,109 @@ describe('flowerbase-client watch', () => {
175
179
  const byIds = collection.watch({ ids: ['id-1', 'id-2'] })
176
180
  byIds.close()
177
181
  const firstRequest = decodeBaasRequest((global.fetch as jest.Mock).mock.calls[2][0] as string)
178
- expect(firstRequest.arguments[0].pipeline).toEqual([
179
- { $match: { 'documentKey._id': { $in: ['id-1', 'id-2'] } } }
180
- ])
182
+ expect(firstRequest.arguments[0].ids).toEqual(['id-1', 'id-2'])
183
+ expect(firstRequest.arguments[0].filter).toBeUndefined()
181
184
 
182
185
  const byFilter = collection.watch({ filter: { operationType: 'insert' } })
183
186
  byFilter.close()
184
187
  const secondRequest = decodeBaasRequest((global.fetch as jest.Mock).mock.calls[3][0] as string)
185
- expect(secondRequest.arguments[0].pipeline).toEqual([{ $match: { operationType: 'insert' } }])
188
+ expect(secondRequest.arguments[0].filter).toEqual({ operationType: 'insert' })
189
+ expect(secondRequest.arguments[0].ids).toBeUndefined()
190
+ })
191
+
192
+ it('preserves ObjectId values in watch filter payload', async () => {
193
+ global.fetch = jest
194
+ .fn()
195
+ .mockResolvedValueOnce({
196
+ ok: true,
197
+ text: async () => JSON.stringify({
198
+ access_token: 'access',
199
+ refresh_token: 'refresh',
200
+ user_id: 'user-1'
201
+ })
202
+ })
203
+ .mockResolvedValueOnce({
204
+ ok: true,
205
+ text: async () => JSON.stringify({ access_token: 'access' })
206
+ })
207
+ .mockResolvedValue({
208
+ ok: true,
209
+ body: streamFromLines([])
210
+ }) as unknown as typeof fetch
211
+
212
+ const app = new App({ id: 'my-app', baseUrl: 'http://localhost:3000' })
213
+ await app.logIn(Credentials.emailPassword('john@doe.com', 'secret123'))
214
+
215
+ const collection = app.currentUser!.mongoClient('mongodb-atlas').db('testdb').collection('todos')
216
+ const requestId = new ObjectId('69a282a75cd849c244e001ca')
217
+
218
+ const iterator = collection.watch({
219
+ filter: {
220
+ 'fullDocument.requestId': requestId,
221
+ operationType: 'update'
222
+ }
223
+ })
224
+ iterator.close()
225
+
226
+ const request = decodeBaasRequest((global.fetch as jest.Mock).mock.calls[2][0] as string)
227
+ const decodedRequestId = request.arguments[0].filter?.['fullDocument.requestId']
228
+ expect(decodedRequestId).toBeInstanceOf(ObjectId)
229
+ expect((decodedRequestId as ObjectId).toHexString()).toBe(requestId.toHexString())
230
+ })
231
+
232
+ it('rejects pipeline-based watch signature', async () => {
233
+ global.fetch = jest
234
+ .fn()
235
+ .mockResolvedValueOnce({
236
+ ok: true,
237
+ text: async () => JSON.stringify({
238
+ access_token: 'access',
239
+ refresh_token: 'refresh',
240
+ user_id: 'user-1'
241
+ })
242
+ })
243
+ .mockResolvedValueOnce({
244
+ ok: true,
245
+ text: async () => JSON.stringify({ access_token: 'access' })
246
+ })
247
+ .mockResolvedValue({
248
+ ok: true,
249
+ body: streamFromLines([])
250
+ }) as unknown as typeof fetch
251
+
252
+ const app = new App({ id: 'my-app', baseUrl: 'http://localhost:3000' })
253
+ await app.logIn(Credentials.emailPassword('john@doe.com', 'secret123'))
254
+
255
+ const collection = app.currentUser!.mongoClient('mongodb-atlas').db('testdb').collection('todos')
256
+
257
+ expect(() =>
258
+ collection.watch([{ $match: { operationType: 'update', 'fullDocument.type': 'perennial' } }])
259
+ ).toThrow('watch accepts only an options object with "filter" or "ids"')
260
+ })
261
+
262
+ it('rejects unsupported watch option keys', async () => {
263
+ global.fetch = jest
264
+ .fn()
265
+ .mockResolvedValueOnce({
266
+ ok: true,
267
+ text: async () => JSON.stringify({
268
+ access_token: 'access',
269
+ refresh_token: 'refresh',
270
+ user_id: 'user-1'
271
+ })
272
+ })
273
+ .mockResolvedValueOnce({
274
+ ok: true,
275
+ text: async () => JSON.stringify({ access_token: 'access' })
276
+ }) as unknown as typeof fetch
277
+
278
+ const app = new App({ id: 'my-app', baseUrl: 'http://localhost:3000' })
279
+ await app.logIn(Credentials.emailPassword('john@doe.com', 'secret123'))
280
+
281
+ const collection = app.currentUser!.mongoClient('mongodb-atlas').db('testdb').collection('todos')
282
+ expect(() => collection.watch({ fullDocument: 'updateLookup' })).toThrow(
283
+ 'watch options support only "filter" or "ids"'
284
+ )
186
285
  })
187
286
 
188
287
  it('rejects watch options with both ids and filter', async () => {
@@ -209,4 +308,29 @@ describe('flowerbase-client watch', () => {
209
308
  'watch options cannot include both "ids" and "filter"'
210
309
  )
211
310
  })
311
+
312
+ it('rejects $match inside watch filter object', async () => {
313
+ global.fetch = jest
314
+ .fn()
315
+ .mockResolvedValueOnce({
316
+ ok: true,
317
+ text: async () => JSON.stringify({
318
+ access_token: 'access',
319
+ refresh_token: 'refresh',
320
+ user_id: 'user-1'
321
+ })
322
+ })
323
+ .mockResolvedValueOnce({
324
+ ok: true,
325
+ text: async () => JSON.stringify({ access_token: 'access' })
326
+ }) as unknown as typeof fetch
327
+
328
+ const app = new App({ id: 'my-app', baseUrl: 'http://localhost:3000' })
329
+ await app.logIn(Credentials.emailPassword('john@doe.com', 'secret123'))
330
+
331
+ const collection = app.currentUser!.mongoClient('mongodb-atlas').db('testdb').collection('todos')
332
+ expect(() => collection.watch({ filter: { $match: { operationType: 'insert' } } })).toThrow(
333
+ 'watch filter must be a query object, not a $match stage'
334
+ )
335
+ })
212
336
  })
package/src/mongo.ts CHANGED
@@ -36,37 +36,42 @@ export const createMongoClient = (app: App, serviceName: string, userId: string)
36
36
  }
37
37
 
38
38
  const normalizeWatchInput = (input?: unknown) => {
39
+ if (typeof input === 'undefined') {
40
+ return { filter: undefined, ids: undefined }
41
+ }
39
42
  if (Array.isArray(input)) {
40
- return { pipeline: input, options: {} }
43
+ throw new Error('watch accepts only an options object with "filter" or "ids"')
44
+ }
45
+ if (!input || typeof input !== 'object') {
46
+ throw new Error('watch options must be an object')
47
+ }
48
+
49
+ const typed = input as { ids?: unknown[]; filter?: Record<string, unknown>; [key: string]: unknown }
50
+ const keys = Object.keys(typed)
51
+ const hasOnlyAllowedKeys = keys.every((key) => key === 'ids' || key === 'filter')
52
+ if (!hasOnlyAllowedKeys) {
53
+ throw new Error('watch options support only "filter" or "ids"')
41
54
  }
42
- if (input && typeof input === 'object' && ('ids' in input || 'filter' in input)) {
43
- const typed = input as { ids?: unknown[]; filter?: Record<string, unknown>; [key: string]: unknown }
55
+ if (typed.ids || typed.filter) {
44
56
  if (typed.ids && typed.filter) {
45
57
  throw new Error('watch options cannot include both "ids" and "filter"')
46
58
  }
47
- const { ids, filter, ...options } = typed
59
+ const { ids, filter } = typed
60
+ if (filter && typeof filter === 'object' && '$match' in filter) {
61
+ throw new Error('watch filter must be a query object, not a $match stage')
62
+ }
48
63
  if (ids) {
49
- return {
50
- pipeline: [{ $match: { 'documentKey._id': { $in: ids } } }],
51
- options
64
+ if (!Array.isArray(ids)) {
65
+ throw new Error('watch ids must be an array')
52
66
  }
67
+ return { filter: undefined, ids }
53
68
  }
54
69
  if (filter) {
55
- return {
56
- pipeline: [{ $match: filter }],
57
- options
58
- }
59
- }
60
- return { pipeline: [], options }
61
- }
62
- if (input && typeof input === 'object' && ('pipeline' in input || 'options' in input)) {
63
- const typed = input as { pipeline?: unknown[]; options?: Record<string, unknown> }
64
- return {
65
- pipeline: typed.pipeline ?? [],
66
- options: typed.options ?? {}
70
+ return { filter, ids: undefined }
67
71
  }
72
+ return { filter: undefined, ids: undefined }
68
73
  }
69
- return { pipeline: [], options: (input as Record<string, unknown> | undefined) ?? {} }
74
+ throw new Error('watch options must include "filter" or "ids"')
70
75
  }
71
76
 
72
77
  return {
@@ -87,8 +92,8 @@ export const createMongoClient = (app: App, serviceName: string, userId: string)
87
92
  callService('updateMany', [{ filter, update, options }]),
88
93
  deleteOne: (filter, options = {}) => callService('deleteOne', [{ query: filter, options }]),
89
94
  deleteMany: (filter, options = {}) => callService('deleteMany', [{ query: filter, options }]),
90
- watch: (input) => {
91
- const { pipeline, options } = normalizeWatchInput(input)
95
+ watch: (options) => {
96
+ const { filter, ids } = normalizeWatchInput(options)
92
97
  const session = app.getSessionOrThrow(userId)
93
98
  return createWatchIterator({
94
99
  appId: app.id,
@@ -96,8 +101,8 @@ export const createMongoClient = (app: App, serviceName: string, userId: string)
96
101
  accessToken: session.accessToken,
97
102
  database,
98
103
  collection,
99
- pipeline,
100
- options,
104
+ filter,
105
+ ids,
101
106
  timeout: app.timeout
102
107
  })
103
108
  }
package/src/types.ts CHANGED
@@ -36,8 +36,8 @@ export type WatchConfig = {
36
36
  accessToken: string
37
37
  database: string
38
38
  collection: string
39
- pipeline?: unknown[]
40
- options?: Record<string, unknown>
39
+ filter?: Record<string, unknown>
40
+ ids?: unknown[]
41
41
  timeout?: number
42
42
  }
43
43
 
package/src/watch.ts CHANGED
@@ -1,16 +1,22 @@
1
+ import { EJSON } from './bson'
1
2
  import { WatchAsyncIterator, WatchConfig } from './types'
2
3
 
3
4
  const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
4
5
 
5
- const createWatchRequest = ({ database, collection, pipeline = [], options = {} }: WatchConfig) => ({
6
+ const createWatchRequest = ({
7
+ database,
8
+ collection,
9
+ filter,
10
+ ids
11
+ }: WatchConfig) => ({
6
12
  name: 'watch',
7
13
  service: 'mongodb-atlas',
8
14
  arguments: [
9
15
  {
10
16
  database,
11
17
  collection,
12
- pipeline,
13
- options
18
+ ...(filter ? { filter } : {}),
19
+ ...(ids ? { ids } : {})
14
20
  }
15
21
  ]
16
22
  })
@@ -28,7 +34,7 @@ const parseSsePayload = (line: string) => {
28
34
  if (!raw) return null
29
35
 
30
36
  try {
31
- return JSON.parse(raw)
37
+ return EJSON.deserialize(JSON.parse(raw))
32
38
  } catch {
33
39
  return raw
34
40
  }
@@ -65,7 +71,7 @@ export const createWatchIterator = (config: WatchConfig): WatchAsyncIterator<unk
65
71
  const controller = new AbortController()
66
72
  activeController = controller
67
73
  const request = createWatchRequest(config)
68
- const encoded = toBase64(JSON.stringify(request))
74
+ const encoded = toBase64(JSON.stringify(EJSON.serialize(request, { relaxed: false })))
69
75
  const url = `${config.baseUrl}/api/client/v2.0/app/${config.appId}/functions/call?baas_request=${encodeURIComponent(encoded)}`
70
76
 
71
77
  try {