@flowerforce/flowerbase-client 0.1.1-beta.2

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 (63) hide show
  1. package/CHANGELOG.md +0 -0
  2. package/LICENSE +3 -0
  3. package/README.md +198 -0
  4. package/dist/app.d.ts +40 -0
  5. package/dist/app.d.ts.map +1 -0
  6. package/dist/app.js +186 -0
  7. package/dist/bson.d.ts +8 -0
  8. package/dist/bson.d.ts.map +1 -0
  9. package/dist/bson.js +10 -0
  10. package/dist/credentials.d.ts +7 -0
  11. package/dist/credentials.d.ts.map +1 -0
  12. package/dist/credentials.js +24 -0
  13. package/dist/functions.d.ts +3 -0
  14. package/dist/functions.d.ts.map +1 -0
  15. package/dist/functions.js +30 -0
  16. package/dist/http.d.ts +15 -0
  17. package/dist/http.d.ts.map +1 -0
  18. package/dist/http.js +74 -0
  19. package/dist/index.d.ts +7 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +14 -0
  22. package/dist/mongo.d.ts +4 -0
  23. package/dist/mongo.d.ts.map +1 -0
  24. package/dist/mongo.js +61 -0
  25. package/dist/session.d.ts +12 -0
  26. package/dist/session.d.ts.map +1 -0
  27. package/dist/session.js +53 -0
  28. package/dist/session.native.d.ts +14 -0
  29. package/dist/session.native.d.ts.map +1 -0
  30. package/dist/session.native.js +81 -0
  31. package/dist/types.d.ts +73 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +2 -0
  34. package/dist/user.d.ts +17 -0
  35. package/dist/user.d.ts.map +1 -0
  36. package/dist/user.js +30 -0
  37. package/dist/watch.d.ts +3 -0
  38. package/dist/watch.d.ts.map +1 -0
  39. package/dist/watch.js +138 -0
  40. package/jest.config.ts +13 -0
  41. package/package.json +30 -0
  42. package/project.json +11 -0
  43. package/rollup.config.js +17 -0
  44. package/src/__tests__/auth.test.ts +164 -0
  45. package/src/__tests__/compat.test.ts +12 -0
  46. package/src/__tests__/functions.test.ts +76 -0
  47. package/src/__tests__/mongo.test.ts +48 -0
  48. package/src/__tests__/session.test.ts +103 -0
  49. package/src/__tests__/watch.test.ts +138 -0
  50. package/src/app.ts +235 -0
  51. package/src/bson.ts +6 -0
  52. package/src/credentials.ts +24 -0
  53. package/src/functions.ts +32 -0
  54. package/src/http.ts +92 -0
  55. package/src/index.ts +14 -0
  56. package/src/mongo.ts +63 -0
  57. package/src/session.native.ts +98 -0
  58. package/src/session.ts +59 -0
  59. package/src/types.ts +84 -0
  60. package/src/user.ts +39 -0
  61. package/src/watch.ts +150 -0
  62. package/tsconfig.json +34 -0
  63. package/tsconfig.spec.json +13 -0
package/src/types.ts ADDED
@@ -0,0 +1,84 @@
1
+ export type AppConfig = {
2
+ id: string
3
+ baseUrl: string
4
+ timeout?: number
5
+ }
6
+
7
+ export type CredentialsLike =
8
+ | { provider: 'local-userpass'; email: string; password: string }
9
+ | { provider: 'anon-user' }
10
+ | { provider: 'custom-function'; payload: Record<string, unknown> }
11
+
12
+ export type SessionData = {
13
+ accessToken: string
14
+ refreshToken: string
15
+ userId: string
16
+ }
17
+
18
+ export type ProfileData = {
19
+ _id?: string
20
+ identities?: unknown[]
21
+ type?: string
22
+ custom_data?: Record<string, unknown>
23
+ data?: Record<string, unknown>
24
+ }
25
+
26
+ export type FunctionCallPayload = {
27
+ name: string
28
+ arguments: unknown[]
29
+ service?: string
30
+ }
31
+
32
+ export type WatchConfig = {
33
+ appId: string
34
+ baseUrl: string
35
+ accessToken: string
36
+ database: string
37
+ collection: string
38
+ pipeline?: unknown[]
39
+ options?: Record<string, unknown>
40
+ timeout?: number
41
+ }
42
+
43
+ export type WatchAsyncIterator<TChange = unknown> = AsyncIterableIterator<TChange> & {
44
+ close: () => void
45
+ }
46
+
47
+ export interface CollectionLike {
48
+ find: (query?: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>
49
+ findOne: (query?: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>
50
+ insertOne: (document: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>
51
+ updateOne: (
52
+ filter: Record<string, unknown>,
53
+ update: Record<string, unknown>,
54
+ options?: Record<string, unknown>
55
+ ) => Promise<unknown>
56
+ updateMany: (
57
+ filter: Record<string, unknown>,
58
+ update: Record<string, unknown>,
59
+ options?: Record<string, unknown>
60
+ ) => Promise<unknown>
61
+ deleteOne: (filter: Record<string, unknown>, options?: Record<string, unknown>) => Promise<unknown>
62
+ watch: (pipeline?: unknown[], options?: Record<string, unknown>) => WatchAsyncIterator<unknown>
63
+ }
64
+
65
+ export interface MongoDbLike {
66
+ collection: (name: string) => CollectionLike
67
+ }
68
+
69
+ export interface MongoClientLike {
70
+ db: (name: string) => MongoDbLike
71
+ }
72
+
73
+ export interface UserLike {
74
+ id: string
75
+ profile?: {
76
+ email?: string
77
+ [key: string]: unknown
78
+ }
79
+ functions: Record<string, (...args: unknown[]) => Promise<unknown>>
80
+ logOut: () => Promise<void>
81
+ refreshAccessToken: () => Promise<string>
82
+ refreshCustomData: () => Promise<ProfileData>
83
+ mongoClient: (serviceName: string) => MongoClientLike
84
+ }
package/src/user.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { App } from './app'
2
+ import { createFunctionsProxy } from './functions'
3
+ import { createMongoClient } from './mongo'
4
+ import { MongoClientLike, ProfileData, UserLike } from './types'
5
+
6
+ export class User implements UserLike {
7
+ id: string
8
+ profile?: { email?: string;[key: string]: unknown }
9
+ private readonly app: App
10
+
11
+ functions: Record<string, (...args: unknown[]) => Promise<unknown>>
12
+
13
+ constructor(app: App, id: string) {
14
+ this.app = app
15
+ this.id = id
16
+ this.functions = createFunctionsProxy((name, args) => this.app.callFunction(name, args))
17
+ }
18
+
19
+ async logOut() {
20
+ await this.app.logoutUser()
21
+ }
22
+
23
+ async refreshAccessToken() {
24
+ return this.app.refreshAccessToken()
25
+ }
26
+
27
+ async refreshCustomData(): Promise<ProfileData> {
28
+ const profile = await this.app.getProfile()
29
+ this.profile = profile.data
30
+ return profile.custom_data || {}
31
+ }
32
+
33
+ mongoClient(serviceName: string): MongoClientLike {
34
+ if (serviceName !== 'mongodb-atlas') {
35
+ throw new Error(`Unsupported service "${serviceName}"`)
36
+ }
37
+ return createMongoClient(this.app)
38
+ }
39
+ }
package/src/watch.ts ADDED
@@ -0,0 +1,150 @@
1
+ import { WatchAsyncIterator, WatchConfig } from './types'
2
+
3
+ const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
4
+
5
+ const createWatchRequest = ({ database, collection, pipeline = [], options = {} }: WatchConfig) => ({
6
+ name: 'watch',
7
+ service: 'mongodb-atlas',
8
+ arguments: [
9
+ {
10
+ database,
11
+ collection,
12
+ pipeline,
13
+ options
14
+ }
15
+ ]
16
+ })
17
+
18
+ const toBase64 = (input: string) => {
19
+ if (typeof btoa === 'function') {
20
+ return btoa(input)
21
+ }
22
+ throw new Error('Base64 encoder not available in current runtime')
23
+ }
24
+
25
+ const parseSsePayload = (line: string) => {
26
+ if (!line.startsWith('data:')) return null
27
+ const raw = line.slice(5).trim()
28
+ if (!raw) return null
29
+
30
+ try {
31
+ return JSON.parse(raw)
32
+ } catch {
33
+ return raw
34
+ }
35
+ }
36
+
37
+ export const createWatchIterator = (config: WatchConfig): WatchAsyncIterator<unknown> => {
38
+ let closed = false
39
+ let activeController: AbortController | null = null
40
+ const queue: unknown[] = []
41
+ const waiters: Array<(value: IteratorResult<unknown>) => void> = []
42
+
43
+ const enqueue = (value: unknown) => {
44
+ const waiter = waiters.shift()
45
+ if (waiter) {
46
+ waiter({ done: false, value })
47
+ return
48
+ }
49
+ queue.push(value)
50
+ }
51
+
52
+ const close = () => {
53
+ if (closed) return
54
+ closed = true
55
+ activeController?.abort()
56
+ while (waiters.length > 0) {
57
+ const resolve = waiters.shift()
58
+ resolve?.({ done: true, value: undefined })
59
+ }
60
+ }
61
+
62
+ const run = async () => {
63
+ let attempts = 0
64
+ while (!closed) {
65
+ const controller = new AbortController()
66
+ activeController = controller
67
+ const request = createWatchRequest(config)
68
+ const encoded = toBase64(JSON.stringify(request))
69
+ const url = `${config.baseUrl}/api/client/v2.0/app/${config.appId}/functions/call?baas_request=${encodeURIComponent(encoded)}`
70
+
71
+ try {
72
+ const response = await fetch(url, {
73
+ method: 'GET',
74
+ headers: {
75
+ Authorization: `Bearer ${config.accessToken}`,
76
+ Accept: 'text/event-stream'
77
+ },
78
+ signal: controller.signal
79
+ })
80
+
81
+ if (!response.ok || !response.body) {
82
+ throw new Error(`Watch request failed (${response.status})`)
83
+ }
84
+
85
+ attempts = 0
86
+ const reader = response.body.getReader()
87
+ const decoder = new TextDecoder()
88
+ let buffer = ''
89
+
90
+ while (!closed) {
91
+ const { done, value } = await reader.read()
92
+ if (done) break
93
+
94
+ buffer += decoder.decode(value, { stream: true })
95
+ const lines = buffer.split('\n')
96
+ buffer = lines.pop() ?? ''
97
+
98
+ for (const line of lines) {
99
+ const parsed = parseSsePayload(line)
100
+ if (parsed !== null) {
101
+ enqueue(parsed)
102
+ }
103
+ }
104
+ }
105
+ } catch {
106
+ if (closed) {
107
+ break
108
+ }
109
+ }
110
+
111
+ if (closed) {
112
+ break
113
+ }
114
+
115
+ attempts += 1
116
+ const backoff = Math.min(5000, 250 * 2 ** (attempts - 1))
117
+ await sleep(backoff)
118
+ }
119
+ }
120
+
121
+ void run()
122
+
123
+ return {
124
+ [Symbol.asyncIterator]() {
125
+ return this
126
+ },
127
+ next() {
128
+ if (queue.length > 0) {
129
+ return Promise.resolve({ done: false, value: queue.shift() })
130
+ }
131
+
132
+ if (closed) {
133
+ return Promise.resolve({ done: true, value: undefined })
134
+ }
135
+
136
+ return new Promise((resolve) => {
137
+ waiters.push(resolve)
138
+ })
139
+ },
140
+ return() {
141
+ close()
142
+ return Promise.resolve({ done: true, value: undefined })
143
+ },
144
+ throw(error?: unknown) {
145
+ close()
146
+ return Promise.reject(error)
147
+ },
148
+ close
149
+ }
150
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "./dist",
4
+ "rootDir": "./src",
5
+ "module": "commonjs",
6
+ "target": "ES2020",
7
+ "declaration": true,
8
+ "declarationMap": true,
9
+ "strict": true,
10
+ "moduleResolution": "node",
11
+ "esModuleInterop": true,
12
+ "skipLibCheck": true,
13
+ "baseUrl": ".",
14
+ "paths": {
15
+ "*": [
16
+ "../../node_modules/*"
17
+ ]
18
+ },
19
+ "lib": [
20
+ "ES2021",
21
+ "DOM"
22
+ ]
23
+ },
24
+ "include": [
25
+ "src/**/*"
26
+ ],
27
+ "exclude": [
28
+ "node_modules",
29
+ "**/*.test.ts",
30
+ "**/*.spec.ts",
31
+ "jest.config.ts",
32
+ "dist"
33
+ ]
34
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": [
5
+ "node",
6
+ "jest"
7
+ ]
8
+ },
9
+ "include": [
10
+ "src/**/*.test.ts",
11
+ "src/**/*.spec.ts"
12
+ ]
13
+ }