skapi-js 1.0.0-alpha.9 → 1.0.1

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.
@@ -3,7 +3,15 @@ import { extractFormMeta, generateRandom } from '../utils/utils';
3
3
  import validator from '../utils/validator';
4
4
  import { request } from './request';
5
5
  const __index_number_range = 4503599627370496;
6
- function normalizeRecord(record) {
6
+ function fromBase62(str) {
7
+ const base62Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
8
+ let result = 0;
9
+ for (let i = 0; i < str.length; i++) {
10
+ result = result * 62 + base62Chars.indexOf(str[i]);
11
+ }
12
+ return result;
13
+ }
14
+ export function normalizeRecord(record) {
7
15
  function base_decode(chars) {
8
16
  let charset = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
9
17
  return chars.split('').reverse().reduce((prev, curr, i) => prev + (charset.indexOf(curr) * (62 ** i)), 0);
@@ -23,10 +31,17 @@ function normalizeRecord(record) {
23
31
  referenced_count: 0
24
32
  },
25
33
  ip: '',
26
- bin: []
34
+ bin: {}
27
35
  };
28
36
  const keys = {
29
37
  'ip': (r) => {
38
+ if (r.slice(-1) === 'R') {
39
+ output.readonly = true;
40
+ r = r.slice(0, -1);
41
+ }
42
+ else {
43
+ output.readonly = false;
44
+ }
30
45
  output.ip = r;
31
46
  },
32
47
  'rec': (r) => {
@@ -47,10 +62,10 @@ function normalizeRecord(record) {
47
62
  output.table.name = rSplit[0];
48
63
  output.table.access_group = rSplit[2] == '**' ? 'private' : parseInt(rSplit[2]);
49
64
  if (rSplit?.[3]) {
50
- output.table.subscription = {
51
- user_id: rSplit[3],
52
- group: parseInt(rSplit[4])
53
- };
65
+ output.table.subscription = true;
66
+ }
67
+ else {
68
+ output.table.subscription = false;
54
69
  }
55
70
  },
56
71
  'usr_tbl': (r) => {
@@ -59,10 +74,10 @@ function normalizeRecord(record) {
59
74
  output.table.name = rSplit[1];
60
75
  output.table.access_group = rSplit[3] == '**' ? 'private' : parseInt(rSplit[3]);
61
76
  if (rSplit?.[4]) {
62
- output.table.subscription = {
63
- user_id: rSplit[4],
64
- group: parseInt(rSplit[5])
65
- };
77
+ output.table.subscription = true;
78
+ }
79
+ else {
80
+ output.table.subscription = false;
66
81
  }
67
82
  },
68
83
  'idx': (r) => {
@@ -97,7 +112,40 @@ function normalizeRecord(record) {
97
112
  output.reference.referenced_count = r;
98
113
  },
99
114
  'bin': (r) => {
100
- output.bin = r;
115
+ let binObj = {};
116
+ if (Array.isArray(r)) {
117
+ for (let url of r) {
118
+ let path = url.split('/').slice(3).join('/');
119
+ let splitPath = path.split('/');
120
+ let filename = decodeURIComponent(splitPath.slice(-1)[0]);
121
+ let pathKey = decodeURIComponent(splitPath[10]);
122
+ let size = splitPath[9];
123
+ let uploaded = splitPath[8];
124
+ let access_group = splitPath[6] == '**' ? 'private' : parseInt(splitPath[6]);
125
+ access_group = access_group == 0 ? 'public' : access_group == 1 ? 'authorized' : access_group;
126
+ let obj = {
127
+ access_group,
128
+ filename,
129
+ url,
130
+ path,
131
+ size: fromBase62(size),
132
+ uploaded: fromBase62(uploaded),
133
+ getFile: (dataType, progress) => {
134
+ let config = {
135
+ dataType: dataType || 'download',
136
+ progress
137
+ };
138
+ return getFile.bind(this)(url, config);
139
+ }
140
+ };
141
+ if (binObj[pathKey]) {
142
+ binObj[pathKey].push(obj);
143
+ continue;
144
+ }
145
+ binObj[pathKey] = [obj];
146
+ }
147
+ }
148
+ output.bin = binObj;
101
149
  },
102
150
  'data': (r) => {
103
151
  let data = r;
@@ -118,6 +166,9 @@ function normalizeRecord(record) {
118
166
  keys[k](record[k]);
119
167
  }
120
168
  }
169
+ if (record.private_key) {
170
+ this.__private_access_key[output.record_id] = record.private_key;
171
+ }
121
172
  return output;
122
173
  }
123
174
  function normalizeTypedString(v) {
@@ -155,15 +206,18 @@ export async function deleteFiles(params) {
155
206
  if (!Array.isArray(endpoints)) {
156
207
  throw new SkapiError('"endpoints" should be type: array | string.', { code: 'INVALID_PARAMETER' });
157
208
  }
158
- return request.bind(this)('del-files', {
209
+ let updatedRec = request.bind(this)('del-files', {
159
210
  endpoints,
160
211
  storage: 'records'
161
212
  }, { auth: true, method: 'post' });
213
+ return updatedRec.map(r => normalizeRecord.bind(this)(r));
162
214
  }
163
215
  export async function uploadFiles(fileList, params) {
164
216
  await this.__connection;
165
- let { service = this.service, request = 'post' } = params || {};
166
- if (request === 'post') {
217
+ let params_request = params?.request || 'post';
218
+ let nestKey = params?.nestKey || '';
219
+ let service = params?.service || this.service;
220
+ if (params_request === 'post') {
167
221
  if (!params?.record_id) {
168
222
  throw new SkapiError('"record_id" is required.', { code: 'INVALID_PARAMETER' });
169
223
  }
@@ -172,7 +226,7 @@ export async function uploadFiles(fileList, params) {
172
226
  if (service === this.service) {
173
227
  throw new SkapiError('invalid service.', { code: 'INVALID_PARAMETER' });
174
228
  }
175
- if (request !== 'host') {
229
+ if (params_request !== 'host') {
176
230
  throw new SkapiError('invalid request.', { code: 'INVALID_PARAMETER' });
177
231
  }
178
232
  }
@@ -182,11 +236,14 @@ export async function uploadFiles(fileList, params) {
182
236
  if (fileList instanceof HTMLFormElement) {
183
237
  fileList = new FormData(fileList);
184
238
  }
239
+ let formDataKeys = [];
185
240
  if (fileList instanceof FormData) {
186
241
  let fileEntries = [];
187
242
  for (let entry of fileList.entries()) {
188
243
  let value = entry[1];
189
244
  if (value instanceof File) {
245
+ let key = entry[0];
246
+ formDataKeys.push(key);
190
247
  fileEntries.push(value);
191
248
  }
192
249
  }
@@ -199,7 +256,7 @@ export async function uploadFiles(fileList, params) {
199
256
  let getSignedParams = {
200
257
  reserved_key,
201
258
  service,
202
- request
259
+ request: params_request
203
260
  };
204
261
  if (params?.record_id) {
205
262
  getSignedParams.id = params.record_id;
@@ -249,13 +306,17 @@ export async function uploadFiles(fileList, params) {
249
306
  }
250
307
  return result;
251
308
  }
252
- for (let f of fileList) {
309
+ let bin_endpoints = [];
310
+ for (let i = 0; i < fileList.length; i++) {
311
+ let f = fileList[i];
312
+ let key = formDataKeys?.[i] || '';
253
313
  let signedParams = Object.assign({
254
- key: f.name,
314
+ key: params_request === 'host' ? (nestKey ? nestKey + '/' : '') + f.name : key ? key + '/' + f.name : f.name,
255
315
  sizeKey: toBase62(f.size),
256
316
  contentType: f.type || null
257
317
  }, getSignedParams);
258
- let { fields = null, url } = await request.bind(this)('get-signed-url', signedParams, { auth: true });
318
+ let { fields = null, url, cdn } = await request.bind(this)('get-signed-url', signedParams, { auth: true });
319
+ bin_endpoints.push(cdn);
259
320
  let form = new FormData();
260
321
  for (let name in fields) {
261
322
  form.append(name, fields[name]);
@@ -263,6 +324,8 @@ export async function uploadFiles(fileList, params) {
263
324
  form.append('file', f);
264
325
  try {
265
326
  await fetchProgress(url, form, (p) => {
327
+ if (typeof params.progress !== 'function')
328
+ return;
266
329
  params.progress({
267
330
  status: 'upload',
268
331
  progress: p.loaded / p.total * 100,
@@ -280,15 +343,13 @@ export async function uploadFiles(fileList, params) {
280
343
  failed.push(f);
281
344
  }
282
345
  }
283
- return { completed, failed };
346
+ return { completed, failed, bin_endpoints };
284
347
  }
285
348
  export async function getFile(url, config) {
286
349
  if (typeof url !== 'string') {
287
350
  throw new SkapiError('"url" should be type: string.', { code: 'INVALID_PARAMETER' });
288
351
  }
289
- if (!config?.noCdn) {
290
- validator.Url(url);
291
- }
352
+ validator.Url(url);
292
353
  let isValidEndpoint = false;
293
354
  let splitUrl = url.split('/');
294
355
  let host = splitUrl[2];
@@ -312,24 +373,61 @@ export async function getFile(url, config) {
312
373
  }
313
374
  }
314
375
  let service = subdomain ? null : target_key[1];
315
- validator.Params(config, {
316
- expiration: ['number', () => 60],
317
- noCdn: ['boolean', () => false],
318
- dataType: ['base64', 'blob', 'endpoint', 'download', () => 'download']
319
- }, [], ['progress']);
376
+ config = validator.Params(config, {
377
+ expires: 'number',
378
+ dataType: ['base64', 'blob', 'endpoint', 'download', () => 'download'],
379
+ progress: p => p
380
+ });
320
381
  let needAuth = target_key[0] == 'auth';
321
382
  let filename = url.split('/').slice(-1)[0];
322
- if (config?.noCdn || needAuth && (config?.dataType === 'download' || config?.dataType === 'endpoint')) {
383
+ let expires = config?.expires || 0;
384
+ if (expires) {
385
+ if (expires < 0) {
386
+ throw new SkapiError('"config.expires" should be > 0. (seconds)', { code: 'INVALID_PARAMETER' });
387
+ }
323
388
  let params = {
324
389
  request: subdomain ? 'get-host' : 'get',
325
390
  id: subdomain || target_key[5],
326
- key: url
391
+ key: url,
392
+ expires
327
393
  };
328
394
  if (service) {
329
395
  params.service = service;
330
396
  }
331
397
  url = (await request.bind(this)('get-signed-url', params, { auth: true })).url;
332
398
  }
399
+ else if (needAuth) {
400
+ let token = this.session?.idToken?.jwtToken;
401
+ let access_group = target_key[6] === '**' ? '**' : parseInt(target_key[6]);
402
+ if (!token) {
403
+ throw new SkapiError('User login is required.', { code: 'INVALID_REQUEST' });
404
+ }
405
+ else {
406
+ let currTime = Date.now() / 1000;
407
+ if (this.session.idToken.payload.exp < currTime) {
408
+ try {
409
+ await this.authentication().getSession({ refreshToken: true });
410
+ token = this.session?.idToken?.jwtToken;
411
+ }
412
+ catch (err) {
413
+ this.logout();
414
+ throw new SkapiError('User login is required.', { code: 'INVALID_REQUEST' });
415
+ }
416
+ }
417
+ }
418
+ if (access_group === '**') {
419
+ if (this.__user.user_id !== target_key[3]) {
420
+ throw new SkapiError('User has no access.', { code: 'INVALID_REQUEST' });
421
+ }
422
+ }
423
+ else if (this.__user.access_group < access_group) {
424
+ throw new SkapiError('User has no access.', { code: 'INVALID_REQUEST' });
425
+ }
426
+ url += `?t=${token}`;
427
+ }
428
+ if (config?.dataType === 'endpoint') {
429
+ return url;
430
+ }
333
431
  if (config?.dataType === 'download') {
334
432
  let a = document.createElement('a');
335
433
  a.href = url;
@@ -340,20 +438,19 @@ export async function getFile(url, config) {
340
438
  document.body.removeChild(a);
341
439
  return null;
342
440
  }
343
- if (config?.dataType === 'endpoint') {
344
- return url;
345
- }
346
- let blob = await request.bind(this)(url, { service: service || this.service }, { method: 'get', auth: needAuth, contentType: null, responseType: 'blob', fetchOptions: { progress: config?.progress } });
347
- if (config?.dataType === 'base64') {
348
- function blobToBase64(blob) {
349
- return new Promise((resolve, _) => {
441
+ let blob = new Promise(async (res, rej) => {
442
+ try {
443
+ let b = await request.bind(this)(url, { service: service || this.service }, { method: 'get', noParams: true, contentType: null, responseType: 'blob', fetchOptions: { progress: config?.progress } });
444
+ if (config?.dataType === 'base64') {
350
445
  const reader = new FileReader();
351
- reader.onloadend = () => resolve(reader.result);
352
- reader.readAsDataURL(blob);
353
- });
446
+ reader.onloadend = () => res(reader.result);
447
+ reader.readAsDataURL(b);
448
+ }
354
449
  }
355
- return blobToBase64(blob);
356
- }
450
+ catch (err) {
451
+ rej(err);
452
+ }
453
+ });
357
454
  return blob;
358
455
  }
359
456
  export async function getRecords(query, fetchOptions) {
@@ -373,18 +470,7 @@ export async function getRecords(query, fetchOptions) {
373
470
  table: {
374
471
  name: 'string',
375
472
  access_group: ['number', 'private', 'public', 'authorized'],
376
- subscription: {
377
- user_id: (v) => validator.UserId(v, 'User ID in "subscription.user_id"'),
378
- group: (v) => {
379
- if (typeof v !== 'number') {
380
- throw new SkapiError('"subscription.group" should be type: number.', { code: 'INVALID_PARAMETER' });
381
- }
382
- if (v > 99 || v < 0) {
383
- throw new SkapiError('"subscription.group" should be within range: 0 ~ 99.', { code: 'INVALID_PARAMETER' });
384
- }
385
- return v;
386
- }
387
- }
473
+ subscription: (v) => validator.UserId(v, 'User ID in "subscription"')
388
474
  },
389
475
  reference: 'string',
390
476
  index: {
@@ -438,14 +524,17 @@ export async function getRecords(query, fetchOptions) {
438
524
  throw new SkapiError('"index.range" type should match the type of "index.value".', { code: 'INVALID_PARAMETER' });
439
525
  }
440
526
  if (typeof v === 'string') {
441
- return validator.specialChars(v, 'index.value');
527
+ return validator.specialChars(v, 'index.range', false, true);
442
528
  }
443
529
  return v;
444
530
  }
445
531
  },
446
532
  tag: 'string',
447
- private_access_key: 'string'
533
+ private_key: 'string'
448
534
  };
535
+ if (query?.tag) {
536
+ validator.specialChars(query.tag, 'tag', false, true);
537
+ }
449
538
  if (query?.table) {
450
539
  if (query.table.access_group === 'public') {
451
540
  query.table.access_group = 0;
@@ -453,6 +542,9 @@ export async function getRecords(query, fetchOptions) {
453
542
  else if (query.table.access_group === 'authorized') {
454
543
  query.table.access_group = 1;
455
544
  }
545
+ if (query.table?.name) {
546
+ validator.specialChars(query.table.name, 'table name', true, true);
547
+ }
456
548
  if (typeof query.table.access_group === 'number') {
457
549
  if (!this.__user) {
458
550
  if (0 < query.table.access_group) {
@@ -467,6 +559,7 @@ export async function getRecords(query, fetchOptions) {
467
559
  if (query?.index && !query.index?.name) {
468
560
  throw new SkapiError('"index.name" is required when using "index" parameter.', { code: 'INVALID_REQUEST' });
469
561
  }
562
+ let is_reference_fetch = '';
470
563
  if (query?.record_id) {
471
564
  validator.specialChars(query.record_id, 'record_id', false, false);
472
565
  let outputObj = { record_id: query.record_id };
@@ -474,6 +567,9 @@ export async function getRecords(query, fetchOptions) {
474
567
  outputObj.service = query.service;
475
568
  }
476
569
  query = outputObj;
570
+ if (this.__private_access_key[query.record_id]) {
571
+ query.private_key = this.__private_access_key[query.record_id];
572
+ }
477
573
  }
478
574
  else {
479
575
  let ref_user;
@@ -482,26 +578,44 @@ export async function getRecords(query, fetchOptions) {
482
578
  }
483
579
  if (query.reference) {
484
580
  try {
485
- ref_user = validator.UserId(query?.reference);
581
+ ref_user = validator.UserId(query.reference);
486
582
  }
487
583
  catch (err) {
584
+ validator.specialChars(query.reference, 'reference', false, false);
585
+ is_reference_fetch = query.reference;
586
+ if (this.__private_access_key[is_reference_fetch]) {
587
+ query.private_key = this.__private_access_key[is_reference_fetch];
588
+ }
488
589
  }
489
590
  }
490
- query = validator.Params(query || {}, struct, ref_user ? [] : ['table']);
491
- if (query.table?.subscription && !this.session) {
492
- throw new SkapiError('Unsigned users have no access to subscription records.', { code: 'INVALID_REQUEST' });
591
+ let isAdmin = await this.checkAdmin();
592
+ let q = validator.Params(query || {}, struct, ref_user || isAdmin ? [] : ['table']);
593
+ if (typeof q.table !== 'string') {
594
+ if (q.table?.subscription) {
595
+ if (!this.session) {
596
+ throw new SkapiError('Unsigned users have no access to subscription records.', { code: 'INVALID_REQUEST' });
597
+ }
598
+ q.table.subscription = {
599
+ user_id: q.table.subscription,
600
+ group: 1
601
+ };
602
+ }
493
603
  }
604
+ query = q;
494
605
  }
495
- let auth = query.hasOwnProperty('access_group') && query.table.access_group ? true : !!this.__user;
606
+ let auth = query.hasOwnProperty('access_group') && typeof query.table !== 'string' && query.table.access_group ? true : !!this.__user;
496
607
  let result = await request.bind(this)('get-records', query, {
497
608
  fetchOptions,
498
609
  auth,
499
610
  method: auth ? 'post' : 'get'
500
611
  });
501
612
  for (let i in result.list) {
502
- result.list[i] = normalizeRecord(result.list[i]);
613
+ result.list[i] = normalizeRecord.bind(this)(result.list[i]);
503
614
  }
504
615
  ;
616
+ if (is_reference_fetch && result?.reference_private_key) {
617
+ this.__private_access_key[is_reference_fetch] = result.reference_private_key;
618
+ }
505
619
  return result;
506
620
  }
507
621
  export async function postRecord(form, config) {
@@ -525,16 +639,29 @@ export async function postRecord(form, config) {
525
639
  config.table.access_group = 0;
526
640
  }
527
641
  }
642
+ if (typeof config.reference === 'string') {
643
+ config.reference = {
644
+ record_id: config.reference
645
+ };
646
+ }
528
647
  let progress = config.progress || null;
529
- config = validator.Params(config || {}, {
648
+ let reference_private_key = null;
649
+ let config_chkd = validator.Params(config || {}, {
530
650
  record_id: 'string',
651
+ readonly: 'boolean',
531
652
  table: {
532
653
  name: 'string',
533
- subscription_group: ['number', null],
654
+ subscription: 'boolean',
534
655
  access_group: ['number', 'private', 'public', 'authorized']
535
656
  },
536
657
  reference: {
537
- record_id: ['string', null],
658
+ record_id: (v) => {
659
+ validator.specialChars(v, '"reference.record_id"', false, false);
660
+ if (this.__private_access_key[v]) {
661
+ reference_private_key = this.__private_access_key[v];
662
+ }
663
+ return v;
664
+ },
538
665
  reference_limit: (v) => {
539
666
  if (v === null) {
540
667
  return null;
@@ -573,41 +700,64 @@ export async function postRecord(form, config) {
573
700
  return v;
574
701
  }
575
702
  throw new SkapiError(`"tags" should be type: <string | string[]>`, { code: 'INVALID_PARAMETER' });
703
+ },
704
+ remove_bin: (v) => {
705
+ if (!v) {
706
+ return null;
707
+ }
708
+ let arr = [];
709
+ if (Array.isArray(v)) {
710
+ for (let i of v) {
711
+ if (typeof i === 'string') {
712
+ arr.push(i);
713
+ }
714
+ else if (i.url && i.size && i.filename && typeof i.getFile === 'function') {
715
+ arr.push(i.url);
716
+ }
717
+ else {
718
+ throw new SkapiError(`"remove_bin" should be type: <string | BinaryFile[]>`, { code: 'INVALID_PARAMETER' });
719
+ }
720
+ }
721
+ }
722
+ return arr;
576
723
  }
577
724
  }, [], ['response', 'onerror', 'progress'], null);
578
- if (!config?.table && !config?.record_id) {
725
+ if (!config_chkd?.table && !config_chkd?.record_id) {
579
726
  throw new SkapiError('Either "record_id" or "table" should have a value.', { code: 'INVALID_PARAMETER' });
580
727
  }
581
- if (config.table) {
582
- if (config.table.access_group === 'public') {
583
- config.table.access_group = 0;
728
+ if (typeof config_chkd.table !== 'string' && config_chkd.table) {
729
+ if (config_chkd.table.access_group === 'public') {
730
+ config_chkd.table.access_group = 0;
584
731
  }
585
- else if (config.table.access_group === 'authorized') {
586
- config.table.access_group = 1;
732
+ else if (config_chkd.table.access_group === 'authorized') {
733
+ config_chkd.table.access_group = 1;
587
734
  }
588
- if (typeof config.table.access_group === 'number') {
589
- if (!isAdmin && this.user.access_group < config.table.access_group) {
735
+ if (typeof config_chkd.table.access_group === 'number') {
736
+ if (!isAdmin && this.user.access_group < config_chkd.table.access_group) {
590
737
  throw new SkapiError("User has no access", { code: 'INVALID_REQUEST' });
591
738
  }
592
739
  }
593
- if (!config.table.name) {
740
+ if (!config_chkd.table.name) {
594
741
  throw new SkapiError('"table.name" cannot be empty string.', { code: 'INVALID_PARAMETER' });
595
742
  }
743
+ validator.specialChars(config_chkd.table.name, 'table name', true, true);
596
744
  if (isAdmin) {
597
- if (config.table.access_group === 'private') {
745
+ if (config_chkd.table.access_group === 'private') {
598
746
  throw new SkapiError('Service owner cannot write private records.', { code: 'INVALID_REQUEST' });
599
747
  }
600
- if (config.table.hasOwnProperty('subscription_group')) {
601
- throw new SkapiError('Service owner cannot write to subscription table.', { code: 'INVALID_REQUEST' });
602
- }
603
748
  }
604
- if (typeof config.table?.subscription_group === 'number' && config.table.subscription_group < 0 || config.table.subscription_group > 99) {
605
- throw new SkapiError("Subscription group should be within range: 0 ~ 99", { code: 'INVALID_PARAMETER' });
749
+ if (config_chkd.table?.subscription) {
750
+ config_chkd.table.subscription_group = 1;
751
+ delete config_chkd.table.subscription;
606
752
  }
607
753
  }
754
+ config = config_chkd;
608
755
  delete config.response;
609
756
  delete config.onerror;
610
757
  delete config.progress;
758
+ if (reference_private_key) {
759
+ config.reference_private_key = reference_private_key;
760
+ }
611
761
  if (config.index) {
612
762
  if (!config.index.name || typeof config.index.name !== 'string') {
613
763
  throw new SkapiError('"index.name" is required. type: string.', { code: 'INVALID_PARAMETER' });
@@ -629,43 +779,27 @@ export async function postRecord(form, config) {
629
779
  }
630
780
  let options = { auth: true };
631
781
  let postData = null;
782
+ let to_bin = null;
632
783
  if ((form instanceof HTMLFormElement) || (form instanceof FormData) || (form instanceof SubmitEvent)) {
633
- let toConvert = (form instanceof SubmitEvent) ? form.target : form;
634
- let formData = !(form instanceof FormData) ? new FormData(toConvert) : form;
784
+ form = (form instanceof SubmitEvent) ? form.target : form;
635
785
  let formMeta = extractFormMeta(form);
636
- options.meta = config;
637
- if (Object.keys(formMeta.meta).length) {
638
- options.meta.data = formMeta.meta;
639
- }
640
- let formToRemove = {};
641
- for (let [key, value] of formData.entries()) {
642
- if (formMeta.meta.hasOwnProperty(key) && !(value instanceof Blob)) {
643
- let f = formData.getAll(key);
644
- let f_idx = f.indexOf(value);
645
- if (formToRemove.hasOwnProperty(key)) {
646
- formToRemove[key].push(f_idx);
647
- }
648
- else {
649
- formToRemove[key] = [f_idx];
650
- }
651
- }
786
+ if (formMeta.to_bin.length) {
787
+ to_bin = formMeta.to_bin;
652
788
  }
653
- if (Object.keys(formToRemove).length) {
654
- for (let key in formToRemove) {
655
- let values = formData.getAll(key);
656
- let val_len = values.length;
657
- while (val_len--) {
658
- if (formToRemove[key].includes(val_len)) {
659
- values.splice(val_len, 1);
660
- }
661
- }
662
- formData.delete(key);
663
- for (let dat of values) {
664
- formData.append(key, dat, dat instanceof File ? dat.name : null);
665
- }
789
+ if (formMeta.files.length) {
790
+ let formData = new FormData();
791
+ for (let f of formMeta.files) {
792
+ formData.append(f.name, f.file, f.file.name);
793
+ }
794
+ options.meta = config;
795
+ if (Object.keys(formMeta.meta).length) {
796
+ options.meta.data = formMeta.meta;
666
797
  }
798
+ postData = formData;
799
+ }
800
+ else {
801
+ postData = Object.assign({ data: formMeta.meta }, config);
667
802
  }
668
- postData = formData;
669
803
  }
670
804
  else {
671
805
  postData = Object.assign({ data: form }, config);
@@ -676,7 +810,28 @@ export async function postRecord(form, config) {
676
810
  if (Object.keys(fetchOptions).length) {
677
811
  Object.assign(options, { fetchOptions });
678
812
  }
679
- return normalizeRecord(await request.bind(this)('post-record', postData, options));
813
+ let rec = await request.bind(this)('post-record', postData, options);
814
+ if (to_bin) {
815
+ let bin_formData = new FormData();
816
+ for (let f of to_bin) {
817
+ bin_formData.append(f.name, f.file, f.file.name);
818
+ }
819
+ let uploadFileParams = {
820
+ record_id: rec.rec,
821
+ progress
822
+ };
823
+ if (config.hasOwnProperty('service')) {
824
+ uploadFileParams['service'] = config.service;
825
+ }
826
+ let { bin_endpoints } = await uploadFiles.bind(this)(bin_formData, uploadFileParams);
827
+ if (!rec.bin) {
828
+ rec.bin = bin_endpoints;
829
+ }
830
+ else {
831
+ rec.bin.push(...bin_endpoints);
832
+ }
833
+ }
834
+ return normalizeRecord.bind(this)(rec);
680
835
  }
681
836
  export async function getTables(query, fetchOptions) {
682
837
  let res = await request.bind(this)('get-table', validator.Params(query || {}, {
@@ -688,7 +843,7 @@ export async function getTables(query, fetchOptions) {
688
843
  'tbl': 'table',
689
844
  'srvc': 'service'
690
845
  };
691
- if (Array.isArray(res.list)) {
846
+ if (Array.isArray(res?.list)) {
692
847
  for (let t of res.list) {
693
848
  for (let k in convert) {
694
849
  if (t.hasOwnProperty(k)) {
@@ -701,6 +856,9 @@ export async function getTables(query, fetchOptions) {
701
856
  return res;
702
857
  }
703
858
  export async function getIndexes(query, fetchOptions) {
859
+ if (!query?.table) {
860
+ throw new SkapiError('"table" is required.', { code: 'INVALID_PARAMETER' });
861
+ }
704
862
  let p = validator.Params(query || {}, {
705
863
  table: 'string',
706
864
  index: (v) => validator.specialChars(v, 'index name', true, false),
@@ -743,7 +901,7 @@ export async function getIndexes(query, fetchOptions) {
743
901
  'avrg_bool': 'average_bool',
744
902
  'cnt_str': 'string_count'
745
903
  };
746
- if (Array.isArray(res.list)) {
904
+ if (Array.isArray(res?.list)) {
747
905
  res.list = res.list.map((i) => {
748
906
  let iSplit = i.idx.split('/');
749
907
  let resolved = {
@@ -767,7 +925,7 @@ export async function getTags(query, fetchOptions) {
767
925
  tag: 'string',
768
926
  condition: ['gt', 'gte', 'lt', 'lte', '>', '>=', '<', '<=', '=', 'eq', '!=', 'ne']
769
927
  }), Object.assign({ auth: true }, { fetchOptions }));
770
- if (Array.isArray(res.list)) {
928
+ if (Array.isArray(res?.list)) {
771
929
  for (let i in res.list) {
772
930
  let item = res.list[i];
773
931
  let tSplit = item.tag.split('/');
@@ -788,15 +946,17 @@ export async function deleteRecords(params) {
788
946
  if (params?.record_id) {
789
947
  return await request.bind(this)('del-records', {
790
948
  service: params.service || this.service,
791
- record_id: (v => {
792
- let id = validator.specialChars(v, 'record_id', false, false);
949
+ record_id: (id => {
793
950
  if (typeof id === 'string') {
794
951
  return [id];
795
952
  }
953
+ if (!Array.isArray(id)) {
954
+ throw new SkapiError('"record_id" should be type: <string | string[]>', { code: 'INVALID_PARAMETER' });
955
+ }
796
956
  if (id.length > 100) {
797
957
  throw new SkapiError('"record_id" should not exceed 100 items.', { code: 'INVALID_PARAMETER' });
798
958
  }
799
- return id;
959
+ return validator.specialChars(id, 'record_id', false, false);
800
960
  })(params.record_id)
801
961
  }, { auth: true });
802
962
  }
@@ -826,24 +986,28 @@ export async function deleteRecords(params) {
826
986
  },
827
987
  name: 'string',
828
988
  subscription: (v) => {
829
- if (isAdmin) {
989
+ if (isAdmin && typeof params?.table?.subscription === 'string') {
830
990
  return validator.UserId(v, 'User ID in "table.subscription"');
831
991
  }
832
- throw new SkapiError('"table.subscription" is an invalid parameter key.', { code: 'INVALID_PARAMETER' });
833
- },
834
- subscription_group: (v) => {
835
- if (isAdmin && typeof params?.table?.subscription !== 'string') {
836
- throw new SkapiError('"table.subscription" is required.', { code: 'INVALID_PARAMETER' });
837
- }
838
- if (typeof v === 'number') {
839
- if (v >= 0 && v < 99) {
840
- return v;
992
+ if (typeof v === 'boolean') {
993
+ if (v) {
994
+ return this.__user.user_id;
995
+ }
996
+ else {
997
+ return null;
841
998
  }
842
999
  }
843
- throw new SkapiError('Subscription group should be between 0 ~ 99.', { code: 'INVALID_PARAMETER' });
844
- }
1000
+ throw new SkapiError('"table.subscription" is an invalid parameter key.', { code: 'INVALID_PARAMETER' });
1001
+ },
845
1002
  };
846
- params.table = validator.Params(params.table || {}, struct, isAdmin ? [] : ['name']);
1003
+ let table_p = validator.Params(params.table || {}, struct, isAdmin ? [] : ['name']);
1004
+ if (table_p.subscription === null) {
1005
+ delete table_p.subscription;
1006
+ }
1007
+ else {
1008
+ table_p.subscription_group = 1;
1009
+ }
1010
+ params.table = table_p;
847
1011
  }
848
1012
  return await request.bind(this)('del-records', params, { auth: true });
849
1013
  }
@@ -854,7 +1018,7 @@ export function grantPrivateRecordAccess(params) {
854
1018
  if (!params.user_id || Array.isArray(params.user_id) && !params.user_id.length) {
855
1019
  throw new SkapiError(`User ID is required.`, { code: 'INVALID_PARAMETER' });
856
1020
  }
857
- return recordAccess({
1021
+ return recordAccess.bind(this)({
858
1022
  record_id: params.record_id,
859
1023
  user_id: params.user_id || null,
860
1024
  execute: 'add'
@@ -867,18 +1031,23 @@ export function removePrivateRecordAccess(params) {
867
1031
  if (!params.user_id || Array.isArray(params.user_id) && !params.user_id.length) {
868
1032
  throw new SkapiError(`User ID is required.`, { code: 'INVALID_PARAMETER' });
869
1033
  }
870
- return recordAccess({
1034
+ return recordAccess.bind(this)({
871
1035
  record_id: params.record_id,
872
1036
  user_id: params.user_id || null,
873
1037
  execute: 'remove'
874
1038
  });
875
1039
  }
876
1040
  export function listPrivateRecordAccess(params) {
877
- return recordAccess({
1041
+ let list = recordAccess.bind(this)({
878
1042
  record_id: params.record_id,
879
1043
  user_id: params.user_id || null,
880
1044
  execute: 'list'
881
1045
  });
1046
+ list.list = list.list.map((i) => {
1047
+ i.record_id = i.rec_usr.split('/')[0];
1048
+ i.user_id = i.rec_usr.split('/')[1];
1049
+ return i;
1050
+ });
882
1051
  }
883
1052
  export function requestPrivateRecordAccessKey(record_id) {
884
1053
  return request.bind(this)('request-private-access-key', { record_id }, { auth: true });
@@ -894,13 +1063,16 @@ function recordAccess(params) {
894
1063
  }
895
1064
  throw new SkapiError(`User ID is required.`, { code: 'INVALID_PARAMETER' });
896
1065
  }
897
- let id = validator.specialChars(v, 'user id', false, false);
1066
+ let id = v;
898
1067
  if (typeof id === 'string') {
899
- return [id];
1068
+ id = [id];
900
1069
  }
901
1070
  if (id.length > 100) {
902
1071
  throw new SkapiError(`Cannot process more than 100 users at once.`, { code: 'INVALID_REQUEST' });
903
1072
  }
1073
+ for (let i of id) {
1074
+ validator.UserId(i, 'User ID in "user_id"');
1075
+ }
904
1076
  return id;
905
1077
  },
906
1078
  execute: ['add', 'remove', 'list']