1 | ;
|
2 | // Copyright 2015 Google LLC
|
3 | //
|
4 | // Licensed under the Apache License, Version 2.0 (the "License");
|
5 | // you may not use this file except in compliance with the License.
|
6 | // You may obtain a copy of the License at
|
7 | //
|
8 | // http://www.apache.org/licenses/LICENSE-2.0
|
9 | //
|
10 | // Unless required by applicable law or agreed to in writing, software
|
11 | // distributed under the License is distributed on an "AS IS" BASIS,
|
12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13 | // See the License for the specific language governing permissions and
|
14 | // limitations under the License.
|
15 | Object.defineProperty(exports, "__esModule", { value: true });
|
16 | /*!
|
17 | * @module common/service-object
|
18 | */
|
19 | const promisify_1 = require("@google-cloud/promisify");
|
20 | const arrify = require("arrify");
|
21 | const events_1 = require("events");
|
22 | const extend = require("extend");
|
23 | const util_1 = require("./util");
|
24 | /**
|
25 | * ServiceObject is a base class, meant to be inherited from by a "service
|
26 | * object," like a BigQuery dataset or Storage bucket.
|
27 | *
|
28 | * Most of the time, these objects share common functionality; they can be
|
29 | * created or deleted, and you can get or set their metadata.
|
30 | *
|
31 | * By inheriting from this class, a service object will be extended with these
|
32 | * shared behaviors. Note that any method can be overridden when the service
|
33 | * object requires specific behavior.
|
34 | */
|
35 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
36 | class ServiceObject extends events_1.EventEmitter {
|
37 | /*
|
38 | * @constructor
|
39 | * @alias module:common/service-object
|
40 | *
|
41 | * @private
|
42 | *
|
43 | * @param {object} config - Configuration object.
|
44 | * @param {string} config.baseUrl - The base URL to make API requests to.
|
45 | * @param {string} config.createMethod - The method which creates this object.
|
46 | * @param {string=} config.id - The identifier of the object. For example, the
|
47 | * name of a Storage bucket or Pub/Sub topic.
|
48 | * @param {object=} config.methods - A map of each method name that should be inherited.
|
49 | * @param {object} config.methods[].reqOpts - Default request options for this
|
50 | * particular method. A common use case is when `setMetadata` requires a
|
51 | * `PUT` method to override the default `PATCH`.
|
52 | * @param {object} config.parent - The parent service instance. For example, an
|
53 | * instance of Storage if the object is Bucket.
|
54 | */
|
55 | constructor(config) {
|
56 | super();
|
57 | this.metadata = {};
|
58 | this.baseUrl = config.baseUrl;
|
59 | this.parent = config.parent; // Parent class.
|
60 | this.id = config.id; // Name or ID (e.g. dataset ID, bucket name, etc).
|
61 | this.createMethod = config.createMethod;
|
62 | this.methods = config.methods || {};
|
63 | this.interceptors = [];
|
64 | this.pollIntervalMs = config.pollIntervalMs;
|
65 | if (config.methods) {
|
66 | // This filters the ServiceObject instance (e.g. a "File") to only have
|
67 | // the configured methods. We make a couple of exceptions for core-
|
68 | // functionality ("request()" and "getRequestInterceptors()")
|
69 | Object.getOwnPropertyNames(ServiceObject.prototype)
|
70 | .filter(methodName => {
|
71 | return (
|
72 | // All ServiceObjects need `request` and `getRequestInterceptors`.
|
73 | // clang-format off
|
74 | !/^request/.test(methodName) &&
|
75 | !/^getRequestInterceptors/.test(methodName) &&
|
76 | // clang-format on
|
77 | // The ServiceObject didn't redefine the method.
|
78 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
79 | this[methodName] ===
|
80 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
81 | ServiceObject.prototype[methodName] &&
|
82 | // This method isn't wanted.
|
83 | !config.methods[methodName]);
|
84 | })
|
85 | .forEach(methodName => {
|
86 | // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
87 | this[methodName] = undefined;
|
88 | });
|
89 | }
|
90 | }
|
91 | create(optionsOrCallback, callback) {
|
92 | // eslint-disable-next-line @typescript-eslint/no-this-alias
|
93 | const self = this;
|
94 | const args = [this.id];
|
95 | if (typeof optionsOrCallback === 'function') {
|
96 | callback = optionsOrCallback;
|
97 | }
|
98 | if (typeof optionsOrCallback === 'object') {
|
99 | args.push(optionsOrCallback);
|
100 | }
|
101 | // Wrap the callback to return *this* instance of the object, not the
|
102 | // newly-created one.
|
103 | // tslint: disable-next-line no-any
|
104 | function onCreate(...args) {
|
105 | const [err, instance] = args;
|
106 | if (!err) {
|
107 | self.metadata = instance.metadata;
|
108 | args[1] = self; // replace the created `instance` with this one.
|
109 | }
|
110 | callback(...args);
|
111 | }
|
112 | args.push(onCreate);
|
113 | // eslint-disable-next-line prefer-spread
|
114 | this.createMethod.apply(null, args);
|
115 | }
|
116 | delete(optionsOrCallback, cb) {
|
117 | const [options, callback] = util_1.util.maybeOptionsOrCallback(optionsOrCallback, cb);
|
118 | const ignoreNotFound = options.ignoreNotFound;
|
119 | delete options.ignoreNotFound;
|
120 | const methodConfig = (typeof this.methods.delete === 'object' && this.methods.delete) || {};
|
121 | const reqOpts = extend(true, {
|
122 | method: 'DELETE',
|
123 | uri: '',
|
124 | }, methodConfig.reqOpts, {
|
125 | qs: options,
|
126 | });
|
127 | // The `request` method may have been overridden to hold any special
|
128 | // behavior. Ensure we call the original `request` method.
|
129 | ServiceObject.prototype.request.call(this, reqOpts, (err, ...args) => {
|
130 | if (err) {
|
131 | if (err.code === 404 && ignoreNotFound) {
|
132 | err = null;
|
133 | }
|
134 | }
|
135 | callback(err, ...args);
|
136 | });
|
137 | }
|
138 | exists(optionsOrCallback, cb) {
|
139 | const [options, callback] = util_1.util.maybeOptionsOrCallback(optionsOrCallback, cb);
|
140 | this.get(options, err => {
|
141 | if (err) {
|
142 | if (err.code === 404) {
|
143 | callback(null, false);
|
144 | }
|
145 | else {
|
146 | callback(err);
|
147 | }
|
148 | return;
|
149 | }
|
150 | callback(null, true);
|
151 | });
|
152 | }
|
153 | get(optionsOrCallback, cb) {
|
154 | // eslint-disable-next-line @typescript-eslint/no-this-alias
|
155 | const self = this;
|
156 | const [opts, callback] = util_1.util.maybeOptionsOrCallback(optionsOrCallback, cb);
|
157 | const options = Object.assign({}, opts);
|
158 | const autoCreate = options.autoCreate && typeof this.create === 'function';
|
159 | delete options.autoCreate;
|
160 | function onCreate(err, instance, apiResponse) {
|
161 | if (err) {
|
162 | if (err.code === 409) {
|
163 | self.get(options, callback);
|
164 | return;
|
165 | }
|
166 | callback(err, null, apiResponse);
|
167 | return;
|
168 | }
|
169 | callback(null, instance, apiResponse);
|
170 | }
|
171 | this.getMetadata(options, (err, metadata) => {
|
172 | if (err) {
|
173 | if (err.code === 404 && autoCreate) {
|
174 | const args = [];
|
175 | if (Object.keys(options).length > 0) {
|
176 | args.push(options);
|
177 | }
|
178 | args.push(onCreate);
|
179 | self.create(...args);
|
180 | return;
|
181 | }
|
182 | callback(err, null, metadata);
|
183 | return;
|
184 | }
|
185 | callback(null, self, metadata);
|
186 | });
|
187 | }
|
188 | getMetadata(optionsOrCallback, cb) {
|
189 | const [options, callback] = util_1.util.maybeOptionsOrCallback(optionsOrCallback, cb);
|
190 | const methodConfig = (typeof this.methods.getMetadata === 'object' &&
|
191 | this.methods.getMetadata) ||
|
192 | {};
|
193 | const reqOpts = extend(true, {
|
194 | uri: '',
|
195 | }, methodConfig.reqOpts, {
|
196 | qs: options,
|
197 | });
|
198 | // The `request` method may have been overridden to hold any special
|
199 | // behavior. Ensure we call the original `request` method.
|
200 | ServiceObject.prototype.request.call(this, reqOpts, (err, body, res) => {
|
201 | this.metadata = body;
|
202 | callback(err, this.metadata, res);
|
203 | });
|
204 | }
|
205 | /**
|
206 | * Return the user's custom request interceptors.
|
207 | */
|
208 | getRequestInterceptors() {
|
209 | // Interceptors should be returned in the order they were assigned.
|
210 | const localInterceptors = this.interceptors
|
211 | .filter(interceptor => typeof interceptor.request === 'function')
|
212 | .map(interceptor => interceptor.request);
|
213 | return this.parent.getRequestInterceptors().concat(localInterceptors);
|
214 | }
|
215 | setMetadata(metadata, optionsOrCallback, cb) {
|
216 | const [options, callback] = util_1.util.maybeOptionsOrCallback(optionsOrCallback, cb);
|
217 | const methodConfig = (typeof this.methods.setMetadata === 'object' &&
|
218 | this.methods.setMetadata) ||
|
219 | {};
|
220 | const reqOpts = extend(true, {}, {
|
221 | method: 'PATCH',
|
222 | uri: '',
|
223 | }, methodConfig.reqOpts, {
|
224 | json: metadata,
|
225 | qs: options,
|
226 | });
|
227 | // The `request` method may have been overridden to hold any special
|
228 | // behavior. Ensure we call the original `request` method.
|
229 | ServiceObject.prototype.request.call(this, reqOpts, (err, body, res) => {
|
230 | this.metadata = body;
|
231 | callback(err, this.metadata, res);
|
232 | });
|
233 | }
|
234 | request_(reqOpts, callback) {
|
235 | reqOpts = extend(true, {}, reqOpts);
|
236 | const isAbsoluteUrl = reqOpts.uri.indexOf('http') === 0;
|
237 | const uriComponents = [this.baseUrl, this.id || '', reqOpts.uri];
|
238 | if (isAbsoluteUrl) {
|
239 | uriComponents.splice(0, uriComponents.indexOf(reqOpts.uri));
|
240 | }
|
241 | reqOpts.uri = uriComponents
|
242 | .filter(x => x.trim()) // Limit to non-empty strings.
|
243 | .map(uriComponent => {
|
244 | const trimSlashesRegex = /^\/*|\/*$/g;
|
245 | return uriComponent.replace(trimSlashesRegex, '');
|
246 | })
|
247 | .join('/');
|
248 | const childInterceptors = arrify(reqOpts.interceptors_);
|
249 | const localInterceptors = [].slice.call(this.interceptors);
|
250 | reqOpts.interceptors_ = childInterceptors.concat(localInterceptors);
|
251 | if (reqOpts.shouldReturnStream) {
|
252 | return this.parent.requestStream(reqOpts);
|
253 | }
|
254 | this.parent.request(reqOpts, callback);
|
255 | }
|
256 | request(reqOpts, callback) {
|
257 | this.request_(reqOpts, callback);
|
258 | }
|
259 | /**
|
260 | * Make an authenticated API request.
|
261 | *
|
262 | * @param {object} reqOpts - Request options that are passed to `request`.
|
263 | * @param {string} reqOpts.uri - A URI relative to the baseUrl.
|
264 | */
|
265 | requestStream(reqOpts) {
|
266 | const opts = extend(true, reqOpts, { shouldReturnStream: true });
|
267 | return this.request_(opts);
|
268 | }
|
269 | }
|
270 | exports.ServiceObject = ServiceObject;
|
271 | promisify_1.promisifyAll(ServiceObject, { exclude: ['getRequestInterceptors'] });
|
272 | //# sourceMappingURL=service-object.js.map |
\ | No newline at end of file |