@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.
- package/CHANGELOG.md +0 -0
- package/LICENSE +3 -0
- package/README.md +198 -0
- package/dist/app.d.ts +40 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +186 -0
- package/dist/bson.d.ts +8 -0
- package/dist/bson.d.ts.map +1 -0
- package/dist/bson.js +10 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +24 -0
- package/dist/functions.d.ts +3 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +30 -0
- package/dist/http.d.ts +15 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/http.js +74 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +14 -0
- package/dist/mongo.d.ts +4 -0
- package/dist/mongo.d.ts.map +1 -0
- package/dist/mongo.js +61 -0
- package/dist/session.d.ts +12 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +53 -0
- package/dist/session.native.d.ts +14 -0
- package/dist/session.native.d.ts.map +1 -0
- package/dist/session.native.js +81 -0
- package/dist/types.d.ts +73 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/user.d.ts +17 -0
- package/dist/user.d.ts.map +1 -0
- package/dist/user.js +30 -0
- package/dist/watch.d.ts +3 -0
- package/dist/watch.d.ts.map +1 -0
- package/dist/watch.js +138 -0
- package/jest.config.ts +13 -0
- package/package.json +30 -0
- package/project.json +11 -0
- package/rollup.config.js +17 -0
- package/src/__tests__/auth.test.ts +164 -0
- package/src/__tests__/compat.test.ts +12 -0
- package/src/__tests__/functions.test.ts +76 -0
- package/src/__tests__/mongo.test.ts +48 -0
- package/src/__tests__/session.test.ts +103 -0
- package/src/__tests__/watch.test.ts +138 -0
- package/src/app.ts +235 -0
- package/src/bson.ts +6 -0
- package/src/credentials.ts +24 -0
- package/src/functions.ts +32 -0
- package/src/http.ts +92 -0
- package/src/index.ts +14 -0
- package/src/mongo.ts +63 -0
- package/src/session.native.ts +98 -0
- package/src/session.ts +59 -0
- package/src/types.ts +84 -0
- package/src/user.ts +39 -0
- package/src/watch.ts +150 -0
- package/tsconfig.json +34 -0
- 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
|
+
}
|