@matech/thebigpos-sdk 3.7.0 → 3.8.0-stag-3544

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.
@@ -1,209 +1,56 @@
1
- /*
2
- Post-generation fixup for the generated TypeScript SDK.
1
+ /*
2
+ This script is meant to run after the SDK has been generated.
3
3
 
4
- swagger-typescript-api does not propagate the `application/json-patch+json`
5
- request content type into the generated code, so PATCH methods that use JSON
6
- Patch would otherwise go out with the wrong Content-Type. This script restores
7
- it by reading the OpenAPI spec the SDK was generated from and, for every
8
- generated PATCH method, setting:
4
+ - It updates the generated code to ensure that all PATCH methods use ContentType.JsonPatch.
5
+ - It also ensures that the ContentType enum includes JsonPatch and modifies the Operation interface
6
+ to allow any value for the `value` property instead of just object or null.
9
7
 
10
- - ContentType.JsonPatch -> when the spec's requestBody for that path
11
- advertises application/json-patch+json
12
- - ContentType.Json -> for every other PATCH method
13
- (e.g. updateLoan, which the backend declares as
14
- [Consumes("application/json")])
15
-
16
- The decision is taken entirely from the spec — there is no hard-coded list of
17
- endpoints to keep in sync. It also ensures the ContentType enum includes
18
- JsonPatch and relaxes the Operation interface `value` to accept any value.
19
-
20
- It additionally teaches `createFormData` to recognise React Native files. The
21
- generator assumes a browser and detects file parts with `instanceof Blob ||
22
- instanceof File`; in React Native a file is a plain `{ uri, name, type }`
23
- object, so without the extra branch every mobile upload is serialised as the
24
- string "[object Object]". This used to live as a patch-package patch in
25
- pos-mobile-v2, which had to be re-created on every SDK bump.
26
-
27
- Usage (pass the SAME spec given to `swagger-typescript-api -p`):
28
- node scripts/apply-json-patch-content-type.js https://api.thebigpos.dev/swagger/<version>/swagger.json
29
- node scripts/apply-json-patch-content-type.js ./swagger.json
30
- SWAGGER_SPEC=<url-or-file> node scripts/apply-json-patch-content-type.js
31
-
32
- Design notes:
33
- - The rewrite is scoped to a single generated method block at a time
34
- (anchored on that method's own `...params,`), so it can never bleed across
35
- method boundaries.
36
- - It sets each method's content type to its target value rather than doing a
37
- conditional replace, so it is idempotent and self-healing (it also repairs
38
- a previously-seen ContentType.JsonPatchPatch double-application).
39
- - If no spec is provided (e.g. a plain commit-hook run), the content-type
40
- rewrite is skipped instead of guessing, so it can never silently corrupt
41
- the generated output.
8
+ This is necessary because the SDK generation does not currently handle these cases correctly.
42
9
  */
43
10
 
44
11
  const fs = require('fs')
45
- const nodePath = require('path')
46
12
 
47
- const SRC = nodePath.resolve(__dirname, '../src/index.ts')
13
+ const path = require('path').resolve(__dirname, '../src/index.ts')
48
14
 
49
- async function loadSpec(source) {
50
- if (!source) return null
51
- if (/^https?:\/\//i.test(source)) {
52
- const res = await fetch(source)
53
- if (!res.ok) throw new Error(`Failed to fetch spec from "${source}": HTTP ${res.status}`)
54
- return res.json()
55
- }
56
- return JSON.parse(fs.readFileSync(nodePath.resolve(source), 'utf8'))
57
- }
58
-
59
- // Collapse spec ("{id}") and generated ("${id}") path params to a common token
60
- // so generated paths can be matched against spec paths regardless of param names.
61
- function normalizePath(p) {
62
- return p.replace(/\$\{[^}]+\}/g, '{}').replace(/\{[^}]+\}/g, '{}')
15
+ if (!fs.existsSync(path)) {
16
+ console.error(`Error: File not found at path "${path}". Please ensure the SDK has been generated.`)
17
+ process.exit(1)
63
18
  }
64
-
65
- function jsonPatchPaths(spec) {
66
- const set = new Set()
67
- for (const [route, ops] of Object.entries(spec.paths || {})) {
68
- const content = ops && ops.patch && ops.patch.requestBody && ops.patch.requestBody.content
69
- if (content && Object.prototype.hasOwnProperty.call(content, 'application/json-patch+json')) {
70
- set.add(normalizePath(route))
71
- }
72
- }
73
- return set
19
+ let content
20
+ try {
21
+ content = fs.readFileSync(path, 'utf8')
22
+ } catch (err) {
23
+ console.error(`Error: Unable to read file at path "${path}". Details: ${err.message}`)
24
+ process.exit(1)
74
25
  }
75
26
 
76
- function setContentType(block, member) {
77
- const target = `type: ContentType.${member}`
78
- if (/type:\s*ContentType\.\w+/.test(block)) return block.replace(/type:\s*ContentType\.\w+/, target)
79
- if (/type:\s*"[^"]*"/.test(block)) return block.replace(/type:\s*"[^"]*"/, target)
80
- if (/\n(\s*)format:/.test(block)) return block.replace(/\n(\s*)format:/, `\n$1${target},\n$1format:`)
81
- return block.replace(/\n(\s*)\.\.\.params,/, `\n$1${target},\n$1...params,`)
82
- }
27
+ // Update PATCH methods to use ContentType.JsonPatch
28
+ content = content.replace(
29
+ /(method:\s*"PATCH"[\s\S]+?)type:\s*ContentType\.Json/g,
30
+ (match) => match.replace('ContentType.Json', 'ContentType.JsonPatch')
31
+ )
83
32
 
84
- function ensureJsonPatchEnum(content) {
85
- return content.replace(/export enum ContentType\s*{([\s\S]*?)}/, (match, enumBody) => {
33
+ // Ensure JsonPatch is included in the ContentType enum
34
+ content = content.replace(
35
+ /export enum ContentType\s*{([\s\S]*?)}/,
36
+ (match, enumBody) => {
86
37
  if (enumBody.includes('JsonPatch')) return match
87
- return `export enum ContentType {\n JsonPatch = "application/json-patch+json",\n ${enumBody.trim()}\n}`
88
- })
89
- }
38
+ const insertion = ` JsonPatch = "application/json-patch+json",\n `
39
+ return `export enum ContentType {\n${insertion}${enumBody.trim()}\n}`
40
+ }
41
+ )
90
42
 
91
- function relaxOperationValue(content) {
92
- return content.replace(/export interface Operation\s*{([\s\S]*?)}/, (match, body) => {
43
+ // Fix the Operation interface to allow any value
44
+ content = content.replace(
45
+ /export interface Operation\s*{([\s\S]*?)}/,
46
+ (match, body) => {
93
47
  const updated = body.replace(
94
48
  /value\?:\s*object\s*\|?\s*null?;/,
95
49
  'value?: string | number | boolean | null | object;'
96
50
  )
97
51
  return `export interface Operation {\n ${updated.trim()}\n}`
98
- })
99
- }
100
-
101
- const RN_FILE_MARKER = 'isReactNativeFile'
102
-
103
- // swagger-typescript-api assumes a browser: it detects multipart file parts with
104
- // `instanceof Blob || instanceof File`. React Native has neither in a usable form —
105
- // a file there is a plain `{ uri, name, type }` object. Without this branch those
106
- // objects fall through to `stringifyFormItem` and every upload from the mobile app
107
- // goes out as the string "[object Object]".
108
- //
109
- // The React Native check runs first so the `instanceof` operands are never even
110
- // evaluated on that path.
111
- //
112
- // The pattern is deliberately whitespace-tolerant instead of matching one exact
113
- // line. As generated today that statement is exactly 80 characters wide, which is
114
- // Prettier's default printWidth — so the moment the generator nests it one level
115
- // deeper, or renames `formItem`, Prettier reflows it across two lines. Anchoring
116
- // on the single-line form would then quietly stop matching.
117
- const GENERATED_FILE_CHECK =
118
- /^([ \t]*)const isFileType\s*=\s*formItem instanceof Blob\s*\|\|\s*formItem instanceof File;/m
119
-
120
- function applyReactNativeFileSupport(content) {
121
- if (content.includes(RN_FILE_MARKER)) return content
122
-
123
- // Never fail open. A silent no-op here ships an SDK where every React Native
124
- // upload is serialised as "[object Object]", with nothing in the build to
125
- // suggest anything went wrong.
126
- if (!GENERATED_FILE_CHECK.test(content)) {
127
- throw new Error(
128
- 'applyReactNativeFileSupport: could not find the generated `isFileType` check in ' +
129
- 'createFormData.\n' +
130
- 'swagger-typescript-api has most likely changed its output. Update ' +
131
- 'GENERATED_FILE_CHECK in this script — do not skip this step, or React Native ' +
132
- 'file uploads will break with no visible error.'
133
- )
134
- }
135
-
136
- return content.replace(
137
- GENERATED_FILE_CHECK,
138
- (match, indent) =>
139
- [
140
- `${indent}// React Native files are plain { uri, name, type } objects, not Blob/File.`,
141
- `${indent}const ${RN_FILE_MARKER} =`,
142
- `${indent} !!formItem &&`,
143
- `${indent} typeof formItem === "object" &&`,
144
- `${indent} "uri" in formItem &&`,
145
- `${indent} "name" in formItem &&`,
146
- `${indent} "type" in formItem;`,
147
- `${indent}const isFileType =`,
148
- `${indent} ${RN_FILE_MARKER} || formItem instanceof Blob || formItem instanceof File;`,
149
- ].join('\n')
150
- )
151
- }
152
-
153
- function applyContentTypes(content, patchPaths) {
154
- // Heal any earlier double-application (ContentType.JsonPatchPatch...).
155
- content = content.replace(/ContentType\.JsonPatch(?:Patch)+\b/g, 'ContentType.JsonPatch')
156
-
157
- const METHOD_BLOCK = /[a-zA-Z0-9_]+:\s*\([\s\S]*?\)\s*=>\s*this\.request<[^>]*>\(\{[\s\S]*?\.\.\.params,/g
158
- return content.replace(METHOD_BLOCK, (block) => {
159
- if (!/method:\s*"PATCH"/.test(block)) {
160
- // Non-PATCH methods must never be JSON Patch.
161
- return /type:\s*ContentType\.JsonPatch\b/.test(block)
162
- ? block.replace(/type:\s*ContentType\.JsonPatch\b/, 'type: ContentType.Json')
163
- : block
164
- }
165
- const routeMatch = block.match(/path:\s*`([^`]*)`/)
166
- const route = routeMatch ? normalizePath(routeMatch[1]) : ''
167
- return setContentType(block, patchPaths.has(route) ? 'JsonPatch' : 'Json')
168
- })
169
- }
170
-
171
- ;(async () => {
172
- if (!fs.existsSync(SRC)) {
173
- console.error(`Error: File not found at "${SRC}". Generate the SDK first.`)
174
- process.exit(1)
175
52
  }
53
+ )
176
54
 
177
- const source = process.argv[2] || process.env.SWAGGER_SPEC || process.env.SWAGGER_URL
178
- let spec
179
- try {
180
- spec = await loadSpec(source)
181
- } catch (err) {
182
- console.error(`Error loading OpenAPI spec: ${err.message}`)
183
- process.exit(1)
184
- }
185
-
186
- let content = fs.readFileSync(SRC, 'utf8')
187
- content = ensureJsonPatchEnum(content)
188
- content = relaxOperationValue(content)
189
- content = applyReactNativeFileSupport(content)
190
-
191
- if (!spec) {
192
- console.warn(
193
- 'No OpenAPI spec provided (argument / SWAGGER_SPEC / SWAGGER_URL) — skipping PATCH content-type rewrite.\n' +
194
- 'Re-run with the spec used for generation to apply it, e.g.:\n' +
195
- ' node scripts/apply-json-patch-content-type.js https://api.thebigpos.dev/swagger/<version>/swagger.json'
196
- )
197
- fs.writeFileSync(SRC, content)
198
- return
199
- }
200
-
201
- const patchPaths = jsonPatchPaths(spec)
202
- content = applyContentTypes(content, patchPaths)
203
-
204
- fs.writeFileSync(SRC, content)
205
- console.log(`SDK patch complete: PATCH content types set from the OpenAPI spec (${patchPaths.size} json-patch endpoint(s)).`)
206
- })().catch((err) => {
207
- console.error(err)
208
- process.exit(1)
209
- })
55
+ fs.writeFileSync(path, content)
56
+ console.log('SDK patch complete: All PATCH methods now use ContentType.JsonPatch.')