@pi-r/azure 0.6.5 → 0.7.0

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
@@ -1,10 +1,7 @@
1
- # @pi-r/azure
1
+ ### @pi-r/azure
2
2
 
3
- ## Documentation
3
+ https://e-mc.readthedocs.io/en/latest/cloud/azure.html
4
4
 
5
- - [E-mc](https://e-mc.readthedocs.io/en/latest/cloud/azure.html)
6
- - [squared](https://squared.readthedocs.io)
7
-
8
- ## LICENSE
5
+ ### LICENSE
9
6
 
10
7
  MIT
package/client/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = void 0;
2
+ exports.CLOUD_DOWNLOAD_CHUNK = exports.CLOUD_UPLOAD_CHUNK = exports.CLOUD_UPLOAD_STREAM = exports.executeBatchQuery = exports.executeQuery = exports.deleteObjectsV2 = exports.deleteObjects = exports.setBucketWebsite = exports.createBucketV2 = exports.createBucket = exports.createDatabaseClient = exports.createStorageClient = exports.validateDatabase = exports.validateStorage = void 0;
4
3
  const path = require("path");
5
4
  const storage = require("@azure/storage-blob");
6
5
  const cosmos = require("@azure/cosmos");
@@ -36,7 +35,7 @@ function createStorageClient(credential) {
36
35
  }
37
36
  }
38
37
  else {
39
- credential.accountName || (credential.accountName = /^https:\/\/([a-z\d]+)\./.exec(sharedAccessSignature)?.[1]);
38
+ credential.accountName ||= /^https:\/\/([a-z\d]+)\./.exec(sharedAccessSignature)?.[1];
40
39
  }
41
40
  return new storage.BlobServiceClient(sharedAccessSignature);
42
41
  }
@@ -69,7 +68,7 @@ async function createBucketV2(credential, bucket, access, options, service = 'az
69
68
  break;
70
69
  }
71
70
  const containerClient = createStorageClient.call(this, credential).getContainerClient(bucket);
72
- const setAccessPolicy = async () => containerClient.setAccessPolicy(access).catch(err => this.formatMessage(64, service, ["Unable to configure container", bucket], err, { ...Cloud.LOG_CLOUD_WARN }));
71
+ const setAccessPolicy = async () => containerClient.setAccessPolicy(access).catch((err) => this.formatMessage(64, service, ["Unable to configure container", bucket], err, { ...Cloud.LOG_CLOUD_WARN }));
73
72
  if (await containerClient.exists().catch(() => false)) {
74
73
  if (access) {
75
74
  await setAccessPolicy();
@@ -120,15 +119,15 @@ async function setBucketWebsite(credential, bucket, options, service = 'azure')
120
119
  .then(response => {
121
120
  const { requestId, errorCode } = response;
122
121
  if (errorCode) {
123
- this.formatMessage(64, service, ["Bucket configured" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), { ...Cloud.LOG_CLOUD_WARN });
122
+ this.formatMessage(64, service, ["Bucket website configured" + ' with errors', bucket], (0, types_1.errorMessage)("Error code", errorCode), { ...Cloud.LOG_CLOUD_WARN });
124
123
  }
125
124
  else {
126
- this.formatMessage(64, service, ["Bucket configured", bucket], requestId, { ...Cloud.LOG_CLOUD_COMMAND });
125
+ this.formatMessage(64, service, ["Bucket website configured", bucket], requestId, { ...Cloud.LOG_CLOUD_COMMAND });
127
126
  }
128
127
  return true;
129
128
  })
130
- .catch(err => {
131
- this.formatFail(64, service, ["Unable to configure bucket", bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
129
+ .catch((err) => {
130
+ this.formatFail(64, service, ["Unable to set bucket website", bucket], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: false });
132
131
  return false;
133
132
  });
134
133
  }
@@ -144,11 +143,12 @@ async function deleteObjectsV2(credential, bucket, recursive = true, service = '
144
143
  const tasks = [];
145
144
  let fileCount = 0;
146
145
  for await (const blob of containerClient.listBlobsFlat({ includeUncommitedBlobs: true })) {
147
- if (recursive || !blob.name.includes('/')) {
148
- tasks.push(containerClient.deleteBlob(blob.name, { versionId: blob.versionId }).catch(() => {
149
- --fileCount;
150
- }));
146
+ if (!recursive && blob.name.includes('/')) {
147
+ continue;
151
148
  }
149
+ tasks.push(containerClient.deleteBlob(blob.name, { versionId: blob.versionId }).catch(() => {
150
+ --fileCount;
151
+ }));
152
152
  }
153
153
  fileCount = tasks.length;
154
154
  return this.allSettled(tasks, ["Unable to delete blob", bucket], 64).then(() => {
@@ -172,7 +172,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
172
172
  const cacheValue = { value: this.valueOfKey(credential, 'cache'), sessionKey };
173
173
  let client;
174
174
  const createClient = (name, table) => {
175
- client || (client = createDatabaseClient.call(this, credential));
175
+ client ||= createDatabaseClient.call(this, credential);
176
176
  return client.database(name).container(table);
177
177
  };
178
178
  const closeClient = () => client?.dispose();
@@ -193,7 +193,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
193
193
  let rows, queryString = caching && ignoreCache !== true ? name + '_' + table + '_' : '';
194
194
  if (storedProcedureId && params) {
195
195
  const { options } = item;
196
- if (queryString && (rows = getCache(queryString += Cloud.asString(partitionKey, true) + '_' + storedProcedureId + Cloud.asString(params, true) + Cloud.asString(options, true)))) {
196
+ if (queryString && (rows = getCache(queryString += (partitionKey ? Cloud.asString(partitionKey, true) : '') + '_' + storedProcedureId + '_' + Cloud.asString(params, true) + Cloud.asString(options, true)))) {
197
197
  result[i] = rows;
198
198
  continue;
199
199
  }
@@ -205,7 +205,7 @@ async function executeBatchQuery(credential, batch, sessionKey) {
205
205
  else if (id) {
206
206
  const { options } = item;
207
207
  if (queryString) {
208
- queryString += Cloud.asString(partitionKey, true) + '_' + id + Cloud.asString(options, true);
208
+ queryString += (partitionKey ? Cloud.asString(partitionKey, true) : '') + '_' + id + '_' + Cloud.asString(options, true);
209
209
  if (!update && (rows = getCache(queryString))) {
210
210
  result[i] = rows;
211
211
  continue;
@@ -220,10 +220,10 @@ async function executeBatchQuery(credential, batch, sessionKey) {
220
220
  else {
221
221
  let { options } = item;
222
222
  if (partitionKey) {
223
- (options || (options = {})).partitionKey = partitionKey;
223
+ (options ||= {}).partitionKey = partitionKey;
224
224
  }
225
225
  if (limit > 0) {
226
- (options || (options = {})).maxItemCount = limit;
226
+ (options ||= {}).maxItemCount = limit;
227
227
  }
228
228
  if (query || (0, types_1.isPlainObject)(options)) {
229
229
  if (queryString && (rows = getCache(queryString += (query ? Cloud.asString(query, true) : '') + Cloud.asString(options, true)))) {
@@ -244,3 +244,6 @@ async function executeBatchQuery(credential, batch, sessionKey) {
244
244
  return result;
245
245
  }
246
246
  exports.executeBatchQuery = executeBatchQuery;
247
+ exports.CLOUD_UPLOAD_STREAM = true;
248
+ exports.CLOUD_UPLOAD_CHUNK = true;
249
+ exports.CLOUD_DOWNLOAD_CHUNK = true;
package/download/index.js CHANGED
@@ -1,26 +1,42 @@
1
1
  "use strict";
2
2
  const types_1 = require("@e-mc/types");
3
- const Module = require("@e-mc/module");
3
+ const path = require("path");
4
4
  const Cloud = require("@e-mc/cloud");
5
5
  const client_1 = require("@pi-r/azure");
6
- function download(credential, service = 'azure') {
6
+ module.exports = function download(credential, service = 'azure') {
7
7
  const blobServiceClient = client_1.createStorageClient.call(this, credential);
8
8
  return (data, callback) => {
9
9
  const { bucket: Bucket, download: target } = data;
10
- const Key = target.filename;
10
+ const Key = target.keyname || target.filename;
11
11
  if (!Bucket || !Key) {
12
12
  callback((0, types_1.errorValue)('Missing property', !Bucket ? 'Container' : 'Name'));
13
13
  return;
14
14
  }
15
- const location = Module.joinPath(Bucket, Key);
16
- const blobClient = blobServiceClient.getContainerClient(Bucket);
17
- blobClient.getBlockBlobClient(Key).downloadToBuffer()
18
- .then(value => {
19
- callback(null, value);
15
+ let tempDir;
16
+ const downloadResponse = (err, url) => {
17
+ callback(err, url);
18
+ if (tempDir) {
19
+ queueMicrotask(() => Cloud.removeDir(tempDir));
20
+ }
21
+ };
22
+ const { flags = 0 } = target;
23
+ const containerClient = blobServiceClient.getContainerClient(Bucket);
24
+ const blobClient = containerClient.getBlockBlobClient(Key);
25
+ const blockSize = (flags & 4) && (0, types_1.alignSize)(target.chunkSize, 1024) || 0;
26
+ let diskClient, destination;
27
+ if (blockSize >= 2097152000 && (tempDir = this.getTempDir({ uuidDir: true }))) {
28
+ diskClient = blobClient.downloadToFile(destination = path.join(tempDir, path.basename(target.filename || Key)), 0, undefined, { snapshot: target.versionId, abortSignal: this.signal });
29
+ }
30
+ (diskClient || blobClient.downloadToBuffer(0, 0, { ...target.options, blockSize, concurrency: blockSize > 0 ? target.chunkLimit : undefined, abortSignal: this.signal }))
31
+ .then((value) => {
32
+ downloadResponse(null, diskClient && destination || value);
20
33
  const deleteObject = target.deleteObject;
21
34
  if (deleteObject) {
22
- blobClient.delete((0, types_1.isPlainObject)(deleteObject) ? (deleteObject.abortSignal = this.signal, deleteObject) : { abortSignal: this.signal })
23
- .then(() => this.formatMessage(64, service, "Delete success", location, { ...Cloud.LOG_CLOUD_DELETE }))
35
+ const location = Cloud.joinPath(Bucket, Key);
36
+ containerClient.delete((0, types_1.isPlainObject)(deleteObject) ? (deleteObject.abortSignal = this.signal, deleteObject) : { abortSignal: this.signal })
37
+ .then(() => {
38
+ this.formatMessage(64, service, "Delete success", location, { ...Cloud.LOG_CLOUD_DELETE });
39
+ })
24
40
  .catch((err) => {
25
41
  if (!(err instanceof Error && err.code === 'BlobNotFound')) {
26
42
  this.formatFail(64, service, ["Delete failed", location], err, { ...Cloud.LOG_CLOUD_FAIL, fatal: !!target.active });
@@ -28,8 +44,6 @@ function download(credential, service = 'azure') {
28
44
  });
29
45
  }
30
46
  })
31
- .catch(err => callback(err));
47
+ .catch((err) => downloadResponse(err));
32
48
  };
33
- }
34
-
35
- module.exports = download;
49
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pi-r/azure",
3
- "version": "0.6.5",
3
+ "version": "0.7.0",
4
4
  "description": "Azure cloud functions for E-mc.",
5
5
  "main": "client/index.js",
6
6
  "publishConfig": {
@@ -20,11 +20,11 @@
20
20
  "license": "MIT",
21
21
  "homepage": "https://github.com/anpham6/pi-r#readme",
22
22
  "dependencies": {
23
- "@e-mc/cloud": "^0.8.6",
24
- "@e-mc/module": "^0.8.6",
25
- "@e-mc/types": "^0.8.6",
23
+ "@e-mc/cloud": "^0.9.0",
24
+ "@e-mc/module": "^0.9.0",
25
+ "@e-mc/types": "^0.9.0",
26
26
  "@azure/cosmos": "^4.0.0",
27
- "@azure/identity": "^4.0.1",
27
+ "@azure/identity": "^4.1.0",
28
28
  "@azure/storage-blob": "^12.17.0"
29
29
  }
30
30
  }
@@ -0,0 +1,24 @@
1
+ import type { CloudDatabase, CloudStorage } from '@e-mc/types/lib/cloud';
2
+
3
+ import type { CosmosClientOptions, FeedOptions, PartitionKey, PatchRequestBody, RequestOptions, SqlQuerySpec } from '@azure/cosmos';
4
+
5
+ export type AzureStorage = CloudStorage<AzureStorageCredential, "azure" | "az">;
6
+
7
+ export interface AzureStorageCredential {
8
+ accountName?: string;
9
+ accountKey?: string;
10
+ connectionString?: string;
11
+ sharedAccessSignature?: string;
12
+ }
13
+
14
+ export interface AzureDatabaseQuery<T = FeedOptions | RequestOptions> extends CloudDatabase<string | SqlQuerySpec, T, PatchRequestBody, unknown[], AzureDatabaseCredential> {
15
+ source: "cloud";
16
+ service: "azure" | "az";
17
+ partitionKey?: PartitionKey;
18
+ storedProcedureId?: string;
19
+ }
20
+
21
+ export interface AzureDatabaseCredential extends AuthValue, CosmosClientOptions {
22
+ tenantId?: string;
23
+ clientId?: string;
24
+ }
package/upload/index.js CHANGED
@@ -1,19 +1,19 @@
1
1
  "use strict";
2
2
  const path = require("path");
3
+ const fs = require("fs");
4
+ const stream = require("stream");
3
5
  const util_1 = require("@e-mc/cloud/util");
4
6
  const types_1 = require("@e-mc/types");
5
- const Module = require("@e-mc/module");
6
7
  const Cloud = require("@e-mc/cloud");
7
8
  const client_1 = require("@pi-r/azure");
8
9
  const BUCKET_SESSION = new Set();
9
10
  const BUCKET_RESPONSE = {};
10
- const getBucketKey = (credential, bucket, acl = '') => Module.asString(credential, true) + bucket + '_' + acl;
11
- function upload(credential, service = 'azure') {
11
+ const getBucketKey = (credential, bucket, acl = '') => Cloud.asString(credential, true) + bucket + '_' + acl;
12
+ module.exports = function upload(credential, service = 'azure') {
12
13
  const blobServiceClient = client_1.createStorageClient.call(this, credential);
13
14
  return async (data, callback) => {
14
- var _a, _b;
15
15
  const { bucket, localUri } = data;
16
- const { pathname = '', fileGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
16
+ const { pathname = '', flags = 0, fileGroup, contentType, metadata, tags, endpoint, admin = {}, overwrite, options } = data.upload;
17
17
  const containerClient = blobServiceClient.getContainerClient(bucket);
18
18
  let filename = data.upload.filename || path.basename(localUri), bucketKey;
19
19
  const cleanup = () => {
@@ -30,7 +30,7 @@ function upload(credential, service = 'azure') {
30
30
  const configBucket = admin.configBucket;
31
31
  if (!BUCKET_SESSION.has(bucket)) {
32
32
  const bucketAcl = admin.publicRead ? 'blob' : admin.acl;
33
- const response = BUCKET_RESPONSE[_a = bucketKey = getBucketKey(credential, bucket, bucketAcl)] || (BUCKET_RESPONSE[_a] = client_1.createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create));
33
+ const response = BUCKET_RESPONSE[bucketKey = getBucketKey(credential, bucket, bucketAcl)] ||= client_1.createBucketV2.call(this, credential, bucket, bucketAcl, configBucket?.create);
34
34
  if (!await response) {
35
35
  errorResponse(null);
36
36
  return;
@@ -42,12 +42,19 @@ function upload(credential, service = 'azure') {
42
42
  if (!Array.isArray(retentionPolicy)) {
43
43
  retentionPolicy = [retentionPolicy];
44
44
  }
45
+ try {
46
+ if (this.hasCoerce(service, 'options')) {
47
+ retentionPolicy.forEach(policy => (0, types_1.coerceObject)(policy));
48
+ }
49
+ }
50
+ catch {
51
+ }
45
52
  containerClient.setAccessPolicy('container', retentionPolicy)
46
53
  .then(() => {
47
54
  const { expiresOn, startsOn, permissions } = retentionPolicy[0].accessPolicy;
48
55
  this.formatMessage(64, service, ["Bucket configured" + ' (retention policy)', bucket], expiresOn ? expiresOn.toLocaleString() + ' (expires)' : startsOn ? startsOn.toLocaleString() + ' (starts)' : permissions, { ...Cloud.LOG_CLOUD_COMMAND });
49
56
  })
50
- .catch(err => addLog(err));
57
+ .catch((err) => addLog(err));
51
58
  }
52
59
  if (!overwrite) {
53
60
  try {
@@ -61,7 +68,7 @@ function upload(credential, service = 'azure') {
61
68
  break;
62
69
  }
63
70
  }
64
- const name = pathname ? Module.joinPath(pathname, filename) : filename;
71
+ const name = pathname ? Cloud.joinPath(pathname, filename) : filename;
65
72
  for await (const blob of containerClient.listBlobsFlat({ includeUncommitedBlobs: true })) {
66
73
  if (blob.name === name) {
67
74
  exists = true;
@@ -78,14 +85,50 @@ function upload(credential, service = 'azure') {
78
85
  return;
79
86
  }
80
87
  }
88
+ const chunkSize = flags & 4 ? (0, types_1.alignSize)(data.upload.chunkSize, 1024) : 0;
89
+ const maxSingleShotSize = Math.max(chunkSize, 8388608);
81
90
  const Key = [filename];
82
91
  const Body = [data.buffer];
92
+ const Path = [];
93
+ const Stream = [];
83
94
  const ContentType = [contentType];
95
+ const getChunkSize = () => chunkSize > 0 ? maxSingleShotSize : undefined;
96
+ if (Body[0].length === 0) {
97
+ try {
98
+ if (chunkSize > 0 && fs.statSync(localUri).size > maxSingleShotSize) {
99
+ Path[0] = localUri;
100
+ }
101
+ else if (flags & 2) {
102
+ Stream[0] = fs.createReadStream(localUri, { highWaterMark: getChunkSize(), signal: this.signal });
103
+ }
104
+ else {
105
+ Body[0] = fs.readFileSync(localUri);
106
+ }
107
+ }
108
+ catch (err) {
109
+ errorResponse(err);
110
+ return;
111
+ }
112
+ }
113
+ else if ((flags & 2) || chunkSize > 0) {
114
+ Stream[0] = stream.Readable.from(Body[0], { highWaterMark: getChunkSize() });
115
+ }
84
116
  if (fileGroup) {
85
- const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, addLog);
117
+ const [key, body, type] = (0, util_1.createKeyAndBody)(filename, fileGroup, chunkSize > 0 ? maxSingleShotSize : 0, addLog, flags);
86
118
  Key.push(...key);
87
- Body.push(...body);
88
119
  ContentType.push(...type);
120
+ for (let i = 0; i < body.length; ++i) {
121
+ const item = body[i];
122
+ if (typeof item === 'string') {
123
+ Path[i + 1] = item;
124
+ }
125
+ else if (Buffer.isBuffer(item)) {
126
+ Body[i + 1] = item;
127
+ }
128
+ else {
129
+ Stream[i + 1] = item;
130
+ }
131
+ }
89
132
  }
90
133
  for (let i = 0; i < Key.length; ++i) {
91
134
  const first = i === 0;
@@ -95,28 +138,47 @@ function upload(credential, service = 'azure') {
95
138
  }
96
139
  return;
97
140
  }
98
- const blobName = Module.joinPath(pathname, Key[i]);
141
+ const blobName = Cloud.joinPath(pathname, Key[i]);
99
142
  const params = { ...options, abortSignal: this.signal };
143
+ const headers = params.blobHTTPHeaders ||= {};
100
144
  if (first) {
101
- if ((0, types_1.isPlainObject)(metadata)) {
145
+ if ((0, types_1.isObject)(metadata)) {
102
146
  params.metadata = metadata;
103
147
  }
104
- if ((0, types_1.isPlainObject)(tags)) {
148
+ if ((0, types_1.isObject)(tags)) {
105
149
  params.tags = tags;
106
150
  }
151
+ headers.blobContentType ||= ContentType[i];
152
+ }
153
+ else {
154
+ headers.blobContentType = ContentType[i];
155
+ }
156
+ const blobClient = containerClient.getBlockBlobClient(blobName);
157
+ let uploadClient;
158
+ if (Stream[i]) {
159
+ uploadClient = blobClient.uploadStream(Stream[i], getChunkSize(), chunkSize > 0 ? data.upload.chunkLimit : undefined, params);
160
+ }
161
+ else if (Path[i]) {
162
+ params.maxSingleShotSize = maxSingleShotSize;
163
+ params.concurrency = data.upload.chunkLimit;
164
+ uploadClient = blobClient.uploadFile(Path[i], params);
165
+ }
166
+ else if (chunkSize > 0 && Body[i].byteLength > maxSingleShotSize) {
167
+ params.maxSingleShotSize = maxSingleShotSize;
168
+ params.concurrency = data.upload.chunkLimit;
169
+ uploadClient = blobClient.uploadData(Body[i], params);
107
170
  }
108
- (_b = (params.blobHTTPHeaders || (params.blobHTTPHeaders = {}))).blobContentType || (_b.blobContentType = ContentType[i]);
109
- containerClient.getBlockBlobClient(blobName).upload(Body[i], Body[i].byteLength, params)
171
+ (uploadClient || blobClient.upload(Body[i], Body[i].byteLength, params))
110
172
  .then(result => {
111
173
  let requestUrl = result._response.request.url;
112
- const url = endpoint ? Module.joinPath(endpoint, blobName) : Module.isFile(requestUrl = decodeURIComponent(requestUrl), 'http/s') ? requestUrl : Module.joinPath(`https://${credential.accountName}.blob.core.windows.net`, bucket, blobName);
174
+ const url = endpoint ? Cloud.joinPath(endpoint, blobName) : Cloud.isFile(requestUrl = decodeURIComponent(requestUrl), 'http/s') ? requestUrl : Cloud.joinPath(`https://${credential.accountName}.blob.core.windows.net`, bucket, blobName);
113
175
  this.formatMessage(64, service, "Upload success", url, { ...Cloud.LOG_CLOUD_UPLOAD });
114
176
  if (first) {
115
177
  cleanup();
116
178
  callback(null, url);
117
179
  }
118
180
  })
119
- .catch(err => {
181
+ .catch((err) => {
120
182
  if (first) {
121
183
  errorResponse(err);
122
184
  }
@@ -126,6 +188,4 @@ function upload(credential, service = 'azure') {
126
188
  });
127
189
  }
128
190
  };
129
- }
130
-
131
- module.exports = upload;
191
+ };