@cfworker/cosmos 1.9.0 → 1.11.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/dist/client.d.ts CHANGED
@@ -1,5 +1,8 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ /// <reference types="@cfworker/worker-types" />
1
3
  import { FeedResponse, ItemResponse } from './response.js';
2
4
  import { RetryPolicy } from './retry.js';
5
+ import { SessionContainer } from './session.js';
3
6
  import { Collection, ConsistencyLevel, Database, Document, IndexingDirective, IndexingPolicy, OfferType, PartitionKeyDefinition, Resource } from './types.js';
4
7
  export interface CosmosClientConfig {
5
8
  endpoint: string;
@@ -8,7 +11,7 @@ export interface CosmosClientConfig {
8
11
  consistencyLevel?: ConsistencyLevel;
9
12
  dbId?: string;
10
13
  collId?: string;
11
- sessionToken?: string;
14
+ sessions?: SessionContainer;
12
15
  fetch?: typeof fetch;
13
16
  }
14
17
  export declare class CosmosClient {
@@ -19,7 +22,7 @@ export declare class CosmosClient {
19
22
  private readonly dbId;
20
23
  private readonly collId;
21
24
  private readonly systemFetch;
22
- sessionToken: string;
25
+ readonly sessions: SessionContainer;
23
26
  requestCharges: number;
24
27
  retries: {
25
28
  count: number;
@@ -44,7 +47,7 @@ export declare class CosmosClient {
44
47
  private fetch;
45
48
  private setHeaders;
46
49
  }
47
- export declare type DocumentInit = BufferSource | ReadableStream<Uint8Array> | string | Resource;
50
+ export declare type DocumentInit = BufferSource | ReadableStream | string | Resource;
48
51
  interface CommonArgs {
49
52
  activityId?: string;
50
53
  consistencyLevel?: ConsistencyLevel;
package/dist/client.js CHANGED
@@ -1,27 +1,20 @@
1
1
  import { FeedResponse, ItemResponse } from './response.js';
2
2
  import { defaultRetryPolicy } from './retry.js';
3
+ import { DefaultSessionContainer } from './session.js';
3
4
  import { getSigner } from './signer.js';
4
5
  import { assertArg, escapeNonASCII, uri } from './util.js';
5
6
  export class CosmosClient {
6
- endpoint;
7
- signer;
8
- retryPolicy;
9
- consistencyLevel;
10
- dbId;
11
- collId;
12
- systemFetch;
13
- sessionToken = '';
14
- requestCharges = 0;
15
- retries = { count: 0, delayMs: 0 };
16
7
  constructor(config) {
8
+ this.requestCharges = 0;
9
+ this.retries = { count: 0, delayMs: 0 };
17
10
  this.endpoint = config.endpoint;
18
11
  this.signer = getSigner(config.masterKey);
19
- this.retryPolicy = config.retryPolicy || defaultRetryPolicy;
20
- this.consistencyLevel = config.consistencyLevel || 'Session';
12
+ this.retryPolicy = config.retryPolicy ?? defaultRetryPolicy;
13
+ this.consistencyLevel = config.consistencyLevel ?? 'Session';
21
14
  this.dbId = config.dbId;
22
15
  this.collId = config.collId;
23
- this.systemFetch = config.fetch || fetch.bind(self);
24
- this.sessionToken = config.sessionToken || '';
16
+ this.sessions = config.sessions ?? new DefaultSessionContainer();
17
+ this.systemFetch = config.fetch ?? fetch.bind(self);
25
18
  }
26
19
  async getDatabases(args = {}) {
27
20
  const url = this.endpoint + `/dbs`;
@@ -178,34 +171,30 @@ export class CosmosClient {
178
171
  return fn.call(this, Object.assign({}, args, { continuation }));
179
172
  };
180
173
  }
181
- async fetchWithRetry(request) {
174
+ async fetchWithRetry(request, context) {
182
175
  const retryRequest = request.clone();
183
176
  const response = await this.fetch(request);
184
- const retryContext = request.__retryContext__ || {
185
- attempts: 0,
186
- cumulativeWaitMs: 0,
187
- request,
188
- response
189
- };
190
- retryContext.attempts++;
191
- retryContext.request = retryRequest;
192
- retryContext.response = response;
193
- const { retry, delayMs } = await this.retryPolicy.shouldRetry(retryContext);
177
+ context ??= { attempts: 0, cumulativeWaitMs: 0, request, response };
178
+ context.attempts++;
179
+ context.request = retryRequest;
180
+ context.response = response;
181
+ const { retry, delayMs } = await this.retryPolicy.shouldRetry(context);
194
182
  if (!retry) {
195
183
  return response;
196
184
  }
197
- await new Promise(resolve => setTimeout(resolve, delayMs));
198
- retryContext.cumulativeWaitMs += delayMs;
199
- retryRequest.__retryContext__ = retryContext;
185
+ if (delayMs !== 0) {
186
+ await new Promise(resolve => setTimeout(resolve, delayMs));
187
+ context.cumulativeWaitMs += delayMs;
188
+ this.retries.delayMs += delayMs;
189
+ }
200
190
  this.retries.count++;
201
- this.retries.delayMs += delayMs;
202
- return this.fetchWithRetry(retryContext.request);
191
+ return this.fetchWithRetry(retryRequest, context);
203
192
  }
204
193
  async fetch(request) {
194
+ this.sessions.setRequestSession(request);
205
195
  await this.signer.sign(request);
206
196
  const response = await this.systemFetch(request);
207
- this.sessionToken =
208
- response.headers.get('x-ms-session-token') || this.sessionToken;
197
+ this.sessions.readResponseSession(response);
209
198
  const requestCharge = +(response.headers.get('x-ms-request-charge') || 0);
210
199
  this.requestCharges += requestCharge;
211
200
  return response;
@@ -219,9 +208,6 @@ export class CosmosClient {
219
208
  }
220
209
  const consistencyLevel = args.consistencyLevel || this.consistencyLevel;
221
210
  headers.set('x-ms-consistency-level', consistencyLevel);
222
- if (consistencyLevel === 'Session' && this.sessionToken) {
223
- headers.set('x-ms-session-token', this.sessionToken);
224
- }
225
211
  if (args.continuation) {
226
212
  headers.set('x-ms-continuation', args.continuation);
227
213
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './client.js';
2
2
  export * from './response.js';
3
+ export * from './session.js';
3
4
  export * from './signer.js';
4
5
  export * from './types.js';
5
6
  export * from './util.js';
package/dist/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './client.js';
2
2
  export * from './response.js';
3
+ export * from './session.js';
3
4
  export * from './signer.js';
4
5
  export * from './types.js';
5
6
  export * from './util.js';
@@ -1,10 +1,11 @@
1
+ /// <reference types="@cloudflare/workers-types" />
1
2
  import { PersistedResource } from './types.js';
2
3
  export declare abstract class CosmosResponse {
3
4
  protected readonly response: Response;
4
5
  constructor(response: Response);
5
6
  get status(): number;
6
7
  get headers(): Headers;
7
- get body(): ReadableStream<Uint8Array> | null;
8
+ get body(): ReadableStream | null;
8
9
  get activityId(): string | null;
9
10
  get etag(): string;
10
11
  get requestCharge(): number;
package/dist/response.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export class CosmosResponse {
2
- response;
3
2
  constructor(response) {
4
3
  this.response = response;
5
4
  }
@@ -34,8 +33,6 @@ export class ItemResponse extends CosmosResponse {
34
33
  }
35
34
  }
36
35
  export class FeedResponse extends CosmosResponse {
37
- next;
38
- itemsProperty;
39
36
  constructor(response, next, itemsProperty) {
40
37
  super(response);
41
38
  this.next = next;
package/dist/retry.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="@cloudflare/workers-types" />
1
2
  export interface RetryContext {
2
3
  request: Request;
3
4
  response: Response;
package/dist/retry.js CHANGED
@@ -1,13 +1,14 @@
1
+ import { readSessionNotAvailable } from './session';
1
2
  export class DefaultRetryPolicy {
2
- maxAttempts;
3
- maxCumulativeWaitTimeMs;
4
- defaultRetryDelayMs;
5
3
  constructor(maxAttempts = 10, maxCumulativeWaitTimeMs = 30000, defaultRetryDelayMs = 5000) {
6
4
  this.maxAttempts = maxAttempts;
7
5
  this.maxCumulativeWaitTimeMs = maxCumulativeWaitTimeMs;
8
6
  this.defaultRetryDelayMs = defaultRetryDelayMs;
9
7
  }
10
8
  async shouldRetry(context) {
9
+ if (readSessionNotAvailable(context.response)) {
10
+ return { retry: true, delayMs: 0 };
11
+ }
11
12
  if (context.response.status !== 429 ||
12
13
  context.attempts >= this.maxAttempts ||
13
14
  context.cumulativeWaitMs >= this.maxCumulativeWaitTimeMs) {
@@ -0,0 +1,25 @@
1
+ /// <reference types="@cloudflare/workers-types" />
2
+ export declare function readSessionNotAvailable(response: Response): boolean;
3
+ export declare function getCollectionId({ url, headers }: {
4
+ url: string;
5
+ headers: Headers;
6
+ }): {
7
+ dbId: string;
8
+ collId: string;
9
+ } | null;
10
+ export interface SessionTokens {
11
+ [dbId: string]: {
12
+ [collId: string]: string;
13
+ };
14
+ }
15
+ export interface SessionContainer {
16
+ tokens: SessionTokens;
17
+ setRequestSession(request: Request): void;
18
+ readResponseSession(response: Response): void;
19
+ }
20
+ export declare class DefaultSessionContainer implements SessionContainer {
21
+ readonly tokens: SessionTokens;
22
+ constructor(tokens?: SessionTokens);
23
+ setRequestSession(request: Request): void;
24
+ readResponseSession(response: Response): void;
25
+ }
@@ -0,0 +1,48 @@
1
+ const readSessionNotAvailableSubstatus = '1002';
2
+ export function readSessionNotAvailable(response) {
3
+ return (response.status === 404 &&
4
+ response.headers.get('x-ms-substatus') === readSessionNotAvailableSubstatus);
5
+ }
6
+ export function getCollectionId({ url, headers }) {
7
+ const path = headers.get('x-ms-alt-content-path') ?? new URL(url).pathname.substr(1);
8
+ const [, dbId, colls, collId] = path.split('/');
9
+ if (colls !== 'colls' || !collId) {
10
+ return null;
11
+ }
12
+ return { dbId, collId };
13
+ }
14
+ export class DefaultSessionContainer {
15
+ constructor(tokens = {}) {
16
+ this.tokens = tokens;
17
+ }
18
+ setRequestSession(request) {
19
+ if (request.headers.get('x-ms-consistency-level') !== 'Session') {
20
+ return;
21
+ }
22
+ const ids = getCollectionId(request);
23
+ if (!ids) {
24
+ return;
25
+ }
26
+ const token = this.tokens?.[ids.dbId]?.[ids.collId];
27
+ if (token) {
28
+ request.headers.set('x-ms-session-token', token);
29
+ }
30
+ }
31
+ readResponseSession(response) {
32
+ const ids = getCollectionId(response);
33
+ if (!ids) {
34
+ return;
35
+ }
36
+ if (readSessionNotAvailable(response)) {
37
+ if (this.tokens[ids.dbId]) {
38
+ delete this.tokens[ids.dbId][ids.collId];
39
+ }
40
+ }
41
+ const token = response.headers.get('x-ms-session-token');
42
+ if (!token) {
43
+ return;
44
+ }
45
+ this.tokens[ids.dbId] ??= {};
46
+ this.tokens[ids.dbId][ids.collId] = token;
47
+ }
48
+ }
package/dist/signer.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="@cloudflare/workers-types" />
1
2
  export declare class Signer {
2
3
  private readonly masterkey;
3
4
  private readonly encoder;
package/dist/signer.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import { decode as base64ToArrayBuffer, encode as arrayBufferToBase64 } from 'base64-arraybuffer';
2
2
  export class Signer {
3
- masterkey;
4
- encoder = new TextEncoder();
5
3
  constructor(masterkey) {
4
+ this.encoder = new TextEncoder();
6
5
  const promiseLike = crypto.subtle.importKey('raw', base64ToArrayBuffer(masterkey), { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
7
6
  this.masterkey = Promise.resolve(promiseLike);
8
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cfworker/cosmos",
3
- "version": "1.9.0",
3
+ "version": "1.11.0",
4
4
  "description": "Azure Cosmos DB client for Cloudflare Workers and service workers",
5
5
  "keywords": [
6
6
  "azure",
@@ -37,17 +37,16 @@
37
37
  "test": "cfworker test test/**/*.spec.ts"
38
38
  },
39
39
  "dependencies": {
40
- "@cfworker/worker-types": "^1.9.0",
41
- "@cloudflare/workers-types": "^2.1.0",
42
- "base64-arraybuffer": "^0.2.0"
40
+ "@cfworker/worker-types": "^1.11.0",
41
+ "@cloudflare/workers-types": "^3.2.0",
42
+ "base64-arraybuffer": "^1.0.1"
43
43
  },
44
44
  "devDependencies": {
45
- "@cfworker/dev": "^1.9.0",
46
- "@types/chai": "^4.2.16",
47
- "@types/mocha": "^8.2.2",
45
+ "@cfworker/dev": "^1.11.0",
46
+ "@types/chai": "^4.2.22",
47
+ "@types/mocha": "^9.0.0",
48
48
  "chai": "^4.3.4",
49
- "mocha": "^8.3.2",
50
- "typescript": "^4.4.3"
51
- },
52
- "gitHead": "d333ff940566a7acc384ab791d8b73bed2a9a211"
49
+ "mocha": "^9.1.3",
50
+ "typescript": "^4.5.2"
51
+ }
53
52
  }
package/src/client.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { FeedResponse, ItemResponse } from './response.js';
2
2
  import { defaultRetryPolicy, RetryContext, RetryPolicy } from './retry.js';
3
+ import { DefaultSessionContainer, SessionContainer } from './session.js';
3
4
  import { getSigner, Signer } from './signer.js';
4
5
  import {
5
6
  Collection,
@@ -49,9 +50,9 @@ export interface CosmosClientConfig {
49
50
  collId?: string;
50
51
 
51
52
  /**
52
- * The session token to reuse when ConsistencyLevel is "Session"
53
+ * Reads, writes, and stores session tokens for requests made with ConsistencyLevel is "Session".
53
54
  */
54
- sessionToken?: string;
55
+ sessions?: SessionContainer;
55
56
 
56
57
  /**
57
58
  * System fetch function
@@ -67,19 +68,19 @@ export class CosmosClient {
67
68
  private readonly dbId: string | undefined;
68
69
  private readonly collId: string | undefined;
69
70
  private readonly systemFetch: typeof fetch;
70
- public sessionToken = '';
71
+ public readonly sessions: SessionContainer;
71
72
  public requestCharges = 0;
72
73
  public retries = { count: 0, delayMs: 0 };
73
74
 
74
75
  constructor(config: CosmosClientConfig) {
75
76
  this.endpoint = config.endpoint;
76
77
  this.signer = getSigner(config.masterKey);
77
- this.retryPolicy = config.retryPolicy || defaultRetryPolicy;
78
- this.consistencyLevel = config.consistencyLevel || 'Session';
78
+ this.retryPolicy = config.retryPolicy ?? defaultRetryPolicy;
79
+ this.consistencyLevel = config.consistencyLevel ?? 'Session';
79
80
  this.dbId = config.dbId;
80
81
  this.collId = config.collId;
81
- this.systemFetch = config.fetch || fetch.bind(self);
82
- this.sessionToken = config.sessionToken || '';
82
+ this.sessions = config.sessions ?? new DefaultSessionContainer();
83
+ this.systemFetch = config.fetch ?? fetch.bind(self);
83
84
  }
84
85
 
85
86
  public async getDatabases(
@@ -305,40 +306,37 @@ export class CosmosClient {
305
306
  };
306
307
  }
307
308
 
308
- private async fetchWithRetry(request: Request): Promise<Response> {
309
+ private async fetchWithRetry(
310
+ request: Request,
311
+ context?: RetryContext
312
+ ): Promise<Response> {
309
313
  const retryRequest = request.clone();
310
314
 
311
315
  const response = await this.fetch(request);
312
316
 
313
- const retryContext: RetryContext = (request as any).__retryContext__ || {
314
- attempts: 0,
315
- cumulativeWaitMs: 0,
316
- request,
317
- response
318
- };
319
- retryContext.attempts++;
320
- retryContext.request = retryRequest;
321
- retryContext.response = response;
322
-
323
- const { retry, delayMs } = await this.retryPolicy.shouldRetry(retryContext);
317
+ context ??= { attempts: 0, cumulativeWaitMs: 0, request, response };
318
+ context.attempts++;
319
+ context.request = retryRequest;
320
+ context.response = response;
324
321
 
322
+ const { retry, delayMs } = await this.retryPolicy.shouldRetry(context);
325
323
  if (!retry) {
326
324
  return response;
327
325
  }
328
-
329
- await new Promise(resolve => setTimeout(resolve, delayMs));
330
- retryContext.cumulativeWaitMs += delayMs;
331
- (retryRequest as any).__retryContext__ = retryContext;
326
+ if (delayMs !== 0) {
327
+ await new Promise(resolve => setTimeout(resolve, delayMs));
328
+ context.cumulativeWaitMs += delayMs;
329
+ this.retries.delayMs += delayMs;
330
+ }
332
331
  this.retries.count++;
333
- this.retries.delayMs += delayMs;
334
- return this.fetchWithRetry(retryContext.request);
332
+ return this.fetchWithRetry(retryRequest, context);
335
333
  }
336
334
 
337
335
  private async fetch(request: Request): Promise<Response> {
336
+ this.sessions.setRequestSession(request);
338
337
  await this.signer.sign(request);
339
338
  const response = await this.systemFetch(request);
340
- this.sessionToken =
341
- response.headers.get('x-ms-session-token') || this.sessionToken;
339
+ this.sessions.readResponseSession(response);
342
340
  const requestCharge = +(response.headers.get('x-ms-request-charge') || 0);
343
341
  this.requestCharges += requestCharge;
344
342
  return response;
@@ -353,9 +351,6 @@ export class CosmosClient {
353
351
  }
354
352
  const consistencyLevel = args.consistencyLevel || this.consistencyLevel;
355
353
  headers.set('x-ms-consistency-level', consistencyLevel);
356
- if (consistencyLevel === 'Session' && this.sessionToken) {
357
- headers.set('x-ms-session-token', this.sessionToken);
358
- }
359
354
  if (args.continuation) {
360
355
  headers.set('x-ms-continuation', args.continuation);
361
356
  }
@@ -409,11 +404,7 @@ export class CosmosClient {
409
404
  }
410
405
  }
411
406
 
412
- export type DocumentInit =
413
- | BufferSource
414
- | ReadableStream<Uint8Array>
415
- | string
416
- | Resource;
407
+ export type DocumentInit = BufferSource | ReadableStream | string | Resource;
417
408
 
418
409
  function toBodyInit(obj: DocumentInit): BodyInit {
419
410
  if (
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './client.js';
2
2
  export * from './response.js';
3
+ export * from './session.js';
3
4
  export * from './signer.js';
4
5
  export * from './types.js';
5
6
  export * from './util.js';
package/src/response.ts CHANGED
@@ -53,7 +53,7 @@ export class FeedResponse<T> extends CosmosResponse {
53
53
  }
54
54
 
55
55
  async json(): Promise<T[]> {
56
- const data = await this.response.json();
56
+ const data: any = await this.response.json();
57
57
  return data[this.itemsProperty];
58
58
  }
59
59
  }
package/src/retry.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { readSessionNotAvailable } from './session';
2
+
1
3
  export interface RetryContext {
2
4
  request: Request;
3
5
  response: Response;
@@ -22,6 +24,9 @@ export class DefaultRetryPolicy implements RetryPolicy {
22
24
  ) {}
23
25
 
24
26
  async shouldRetry(context: RetryContext): Promise<RetryInstruction> {
27
+ if (readSessionNotAvailable(context.response)) {
28
+ return { retry: true, delayMs: 0 };
29
+ }
25
30
  if (
26
31
  context.response.status !== 429 ||
27
32
  context.attempts >= this.maxAttempts ||
package/src/session.ts ADDED
@@ -0,0 +1,74 @@
1
+ const readSessionNotAvailableSubstatus = '1002';
2
+
3
+ export function readSessionNotAvailable(response: Response): boolean {
4
+ return (
5
+ response.status === 404 &&
6
+ response.headers.get('x-ms-substatus') === readSessionNotAvailableSubstatus
7
+ );
8
+ }
9
+
10
+ /**
11
+ * Get the database and collection id from the x-ms-alt-content-path header or url.
12
+ * @param requestOrResponse
13
+ */
14
+ export function getCollectionId({
15
+ url,
16
+ headers
17
+ }: {
18
+ url: string;
19
+ headers: Headers;
20
+ }): { dbId: string; collId: string } | null {
21
+ const path =
22
+ headers.get('x-ms-alt-content-path') ?? new URL(url).pathname.substr(1);
23
+ const [, dbId, colls, collId] = path.split('/');
24
+ if (colls !== 'colls' || !collId) {
25
+ return null;
26
+ }
27
+ return { dbId, collId };
28
+ }
29
+
30
+ export interface SessionTokens {
31
+ [dbId: string]: { [collId: string]: string };
32
+ }
33
+
34
+ export interface SessionContainer {
35
+ tokens: SessionTokens;
36
+ setRequestSession(request: Request): void;
37
+ readResponseSession(response: Response): void;
38
+ }
39
+
40
+ export class DefaultSessionContainer implements SessionContainer {
41
+ constructor(public readonly tokens: SessionTokens = {}) {}
42
+
43
+ setRequestSession(request: Request) {
44
+ if (request.headers.get('x-ms-consistency-level') !== 'Session') {
45
+ return;
46
+ }
47
+ const ids = getCollectionId(request);
48
+ if (!ids) {
49
+ return;
50
+ }
51
+ const token = this.tokens?.[ids.dbId]?.[ids.collId];
52
+ if (token) {
53
+ request.headers.set('x-ms-session-token', token);
54
+ }
55
+ }
56
+
57
+ readResponseSession(response: Response) {
58
+ const ids = getCollectionId(response);
59
+ if (!ids) {
60
+ return;
61
+ }
62
+ if (readSessionNotAvailable(response)) {
63
+ if (this.tokens[ids.dbId]) {
64
+ delete this.tokens[ids.dbId][ids.collId];
65
+ }
66
+ }
67
+ const token = response.headers.get('x-ms-session-token');
68
+ if (!token) {
69
+ return;
70
+ }
71
+ this.tokens[ids.dbId] ??= {};
72
+ this.tokens[ids.dbId][ids.collId] = token;
73
+ }
74
+ }
package/tsconfig.json CHANGED
@@ -1,9 +1,14 @@
1
1
  {
2
2
  "extends": "../../tsconfig-base",
3
3
  "compilerOptions": {
4
- "target": "esnext",
5
- "lib": ["esnext", "webworker"],
6
- "types": ["@cfworker/worker-types", "@cloudflare/workers-types"],
4
+ "target": "ES2021",
5
+ "lib": ["esnext"],
6
+ "types": [
7
+ "chai",
8
+ "mocha",
9
+ "@cfworker/worker-types",
10
+ "@cloudflare/workers-types"
11
+ ],
7
12
  "outDir": "dist",
8
13
  "rootDir": "./src"
9
14
  },
package/LICENSE.md DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2020 Jeremy Danyow
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.