@owlmeans/resource 0.1.18-rc.35 → 0.1.18-rc.37

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
@@ -12,7 +12,7 @@ wants is the `resource-choice` decision.
12
12
  ## Installation
13
13
 
14
14
  ```bash
15
- bun add @owlmeans/resource@^0.1.18-rc.35
15
+ bun add @owlmeans/resource@^0.1.18-rc.37
16
16
  ```
17
17
 
18
18
  ## Concepts
@@ -403,7 +403,7 @@ This package ships embedded agent skills under `agent-meta/`. After installing y
403
403
  your project's skill store (`.agents/skills/`):
404
404
 
405
405
  ```sh
406
- npx @owlmeans/agent-skills@^0.1.18-rc.38
406
+ npx @owlmeans/agent-skills@^0.1.18-rc.42
407
407
  ```
408
408
 
409
409
  The embedded files are version-matched to this package release. Do not edit them
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@owlmeans/resource",
4
- "version": "0.1.18-rc.35",
5
- "generatedAt": "2026-09-23T19:57:40.674Z",
4
+ "version": "0.1.18-rc.37",
5
+ "generatedAt": "2026-09-29T08:01:58.119Z",
6
6
  "canonicalRepo": "https://github.com/owlmeans/common",
7
7
  "entries": [
8
8
  {
@@ -8,7 +8,7 @@ user-invocable: false
8
8
  # @owlmeans/resource
9
9
 
10
10
  **Layer:** Core
11
- **Install:** `"@owlmeans/resource": "^0.1.18-rc.35"` in `dependencies`
11
+ **Install:** `"@owlmeans/resource": "^0.1.18-rc.37"` in `dependencies`
12
12
 
13
13
  ## Key Exports
14
14
 
@@ -30,7 +30,7 @@ user-invocable: false
30
30
  | `ResourceDbService<Db, Client>`, `DbLocker<T>` | What a connection service implements: `db`/`client`/`clients`/`config`/`name`/`ensureConfigAlias`/`initialize`, plus `lock`/`unlock` when the backend encrypts fields. |
31
31
  | `ResourceMaker<R, T>` | `(dbAlias?, serviceAlias?) => T` — the signature every resource maker is typed with, so an app registers `makeXResource()` without repeating the argument list. |
32
32
  | `filterObject(obj, keep?)`, `createListSchema` | Drop null and undefined properties, keeping the names listed in `keep`; the AJV schema for a `ListResult<T>` envelope. |
33
- | Errors | `ResourceError`, `UnknownRecordError`, `MisshapedRecord`, `RecordExists`, `RecordUpdateFailed`, `UnsupportedArgumentError`, `UnsupportedMethodError`, `MigrationError`, `MigrationConflict`. |
33
+ | Errors | `ResourceError`, `UnknownRecordError` (404), `MisshapedRecord`, `RecordExists` (409), `RecordUpdateFailed`, `UnsupportedArgumentError`, `UnsupportedMethodError`, `MigrationError`, `MigrationConflict` — the unmarked ones are faults (500). `tests/errors.spec.ts` refuses an exported class whose status was not decided. `UnknownRecordError.id` is everything after the first `/`, so an id or criteria text holding one survives. |
34
34
  | `DbConfig`, `Config`, `Context` | Database config (`cfg.dbs`) types. |
35
35
 
36
36
  ## Usage
@@ -85,6 +85,19 @@ else; the criteria overload takes `{ sort }` to say which match is "the" one:
85
85
  Every method returns the record(s) themselves, never a driver result object — there is no
86
86
  `rowsAffected`/`rowCount` anywhere on this contract.
87
87
 
88
+ ### What an escaped storage error answers
89
+
90
+ The two refusals declare their HTTP status (`static httpStatus`, the `error` skill's rule), so an
91
+ `@owlmeans/server-api` handler that lets one escape answers it: `UnknownRecordError` **404**,
92
+ `RecordExists` **409** (a unique-key violation, a taken id, and a `create` handed an id —
93
+ `id-present` — alike). Every other class of the family is a fault and answers 500. A development
94
+ body rebuilds into the same class on the client; a production body is the incident id alone and
95
+ arrives as `ApiStatusError` with that status (`@owlmeans/api`).
96
+
97
+ So `get` only what the REQUEST addressed. A missing row it did not name — a configuration record,
98
+ a session, an internal lookup — is not the caller's 404: `load` it and throw the fault or the
99
+ refusal that fits (an auth path throws `AuthorizationError`).
100
+
88
101
  ### The criteria language
89
102
 
90
103
  `Criteria<T>` is keyed by the record's own fields, so a typo is a compile error. A dotted key
package/build/errors.d.ts CHANGED
@@ -1,20 +1,33 @@
1
1
  import { ResilientError } from '@owlmeans/error';
2
+ /**
3
+ * The storage refusal family. A class that answers the caller's request declares the HTTP status
4
+ * an `@owlmeans/server-api` boundary answers it with (`static httpStatus`, inherited through the
5
+ * constructor chain): an absent record 404, a taken id or unique key 409. A fault — a misshaped
6
+ * record, an unsupported argument, a failed migration — declares nothing and answers 500.
7
+ */
2
8
  export declare class ResourceError extends ResilientError {
3
9
  static typeName: string;
4
10
  constructor(message?: string);
5
11
  }
12
+ /** The addressed record does not exist (`get`, `update`, `take` of an absent id or criteria). */
6
13
  export declare class UnknownRecordError extends ResourceError {
7
14
  static typeName: string;
15
+ /** The addressed record is absent: answered 404. */
16
+ static httpStatus: number;
8
17
  static readonly idSeparator: string;
9
18
  constructor(id: string);
19
+ /** Everything after the first separator — an id or a criteria description may contain one. */
10
20
  get id(): string;
11
21
  }
12
22
  export declare class MisshapedRecord extends ResourceError {
13
23
  static typeName: string;
14
24
  constructor(msg: string);
15
25
  }
26
+ /** A record under that id or unique key is already stored (`create`, a unique-index violation). */
16
27
  export declare class RecordExists extends ResourceError {
17
28
  static typeName: string;
29
+ /** The id or unique key is taken: answered 409. */
30
+ static httpStatus: number;
18
31
  constructor(msg: string);
19
32
  }
20
33
  export declare class RecordUpdateFailed extends ResourceError {
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAG,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEjD,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,SAAkB;IAEjD,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAE/E,gBAAuB,WAAW,EAAE,MAAM,CAAM;IAEhD,YAAY,EAAE,EAAE,MAAM,EAGrB;IAED,IAAI,EAAE,IAAI,MAAM,CAEf;CACF;AAED,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAuB,QAAQ,SAA0C;IAEzE,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAE/E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAuB,QAAQ,SAAsD;IAErF,YAAY,QAAQ,EAAE,MAAM,EAG3B;CACF;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,OAAuB,QAAQ,SAAoD;IAEnF,YAAY,MAAM,EAAE,MAAM,EAGzB;CACF;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,aAAa;IAClD,OAAuB,QAAQ,SAA+C;IAE9E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AACA,OAAO,EAAG,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEjD;;;;;GAKG;AACH,qBAAa,aAAc,SAAQ,cAAc;IAC/C,OAAuB,QAAQ,SAAkB;IAEjD,YAAY,OAAO,GAAE,MAAgB,EAEpC;CACF;AAED,iGAAiG;AACjG,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAC/E,oDAAoD;IACpD,OAAc,UAAU,SAAM;IAE9B,gBAAuB,WAAW,EAAE,MAAM,CAAM;IAEhD,YAAY,EAAE,EAAE,MAAM,EAGrB;IAED,8FAA8F;IAC9F,IAAI,EAAE,IAAI,MAAM,CAIf;CACF;AAED,qBAAa,eAAgB,SAAQ,aAAa;IAChD,OAAuB,QAAQ,SAA6C;IAE5E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,mGAAmG;AACnG,qBAAa,YAAa,SAAQ,aAAa;IAC7C,OAAuB,QAAQ,SAA0C;IACzE,mDAAmD;IACnD,OAAc,UAAU,SAAM;IAE9B,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,qBAAa,kBAAmB,SAAQ,aAAa;IACnD,OAAuB,QAAQ,SAAgD;IAE/E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED,qBAAa,wBAAyB,SAAQ,aAAa;IACzD,OAAuB,QAAQ,SAAsD;IAErF,YAAY,QAAQ,EAAE,MAAM,EAG3B;CACF;AAED,qBAAa,sBAAuB,SAAQ,aAAa;IACvD,OAAuB,QAAQ,SAAoD;IAEnF,YAAY,MAAM,EAAE,MAAM,EAGzB;CACF;AAED;;;;GAIG;AACH,qBAAa,cAAe,SAAQ,aAAa;IAC/C,OAAuB,QAAQ,SAA4C;IAE3E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,aAAa;IAClD,OAAuB,QAAQ,SAA+C;IAE9E,YAAY,GAAG,EAAE,MAAM,EAGtB;CACF"}
package/build/errors.js CHANGED
@@ -1,19 +1,30 @@
1
1
  import { ResilientError } from '@owlmeans/error';
2
+ /**
3
+ * The storage refusal family. A class that answers the caller's request declares the HTTP status
4
+ * an `@owlmeans/server-api` boundary answers it with (`static httpStatus`, inherited through the
5
+ * constructor chain): an absent record 404, a taken id or unique key 409. A fault — a misshaped
6
+ * record, an unsupported argument, a failed migration — declares nothing and answers 500.
7
+ */
2
8
  export class ResourceError extends ResilientError {
3
9
  static typeName = 'ResourceError';
4
10
  constructor(message = 'error') {
5
11
  super(ResourceError.typeName, `resource:${message}`);
6
12
  }
7
13
  }
14
+ /** The addressed record does not exist (`get`, `update`, `take` of an absent id or criteria). */
8
15
  export class UnknownRecordError extends ResourceError {
9
16
  static typeName = `${ResourceError.typeName}UnknownRecordError`;
17
+ /** The addressed record is absent: answered 404. */
18
+ static httpStatus = 404;
10
19
  static idSeparator = '/';
11
20
  constructor(id) {
12
21
  super(`unknown-record${UnknownRecordError.idSeparator}${id}`);
13
22
  this.type = UnknownRecordError.typeName;
14
23
  }
24
+ /** Everything after the first separator — an id or a criteria description may contain one. */
15
25
  get id() {
16
- return this.message.split(UnknownRecordError.idSeparator)[1];
26
+ const at = this.message.indexOf(UnknownRecordError.idSeparator);
27
+ return at < 0 ? '' : this.message.slice(at + UnknownRecordError.idSeparator.length);
17
28
  }
18
29
  }
19
30
  export class MisshapedRecord extends ResourceError {
@@ -23,8 +34,11 @@ export class MisshapedRecord extends ResourceError {
23
34
  this.type = MisshapedRecord.typeName;
24
35
  }
25
36
  }
37
+ /** A record under that id or unique key is already stored (`create`, a unique-index violation). */
26
38
  export class RecordExists extends ResourceError {
27
39
  static typeName = `${ResourceError.typeName}RecordExists`;
40
+ /** The id or unique key is taken: answered 409. */
41
+ static httpStatus = 409;
28
42
  constructor(msg) {
29
43
  super(`record-exists:${msg}`);
30
44
  this.type = RecordExists.typeName;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,12 +1,13 @@
1
1
  {
2
2
  "name": "@owlmeans/resource",
3
- "version": "0.1.18-rc.35",
3
+ "version": "0.1.18-rc.37",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsc -b",
8
8
  "dev": "sleep 252 && nodemon -e ts,tsx,json --watch src --exec \"tsc -p ./tsconfig.json\"",
9
- "watch": "tsc -b -w --preserveWatchOutput --pretty"
9
+ "watch": "tsc -b -w --preserveWatchOutput --pretty",
10
+ "test": "bun test ./tests"
10
11
  },
11
12
  "main": "build/index.js",
12
13
  "module": "build/index.js",
@@ -22,13 +23,14 @@
22
23
  },
23
24
  "dependencies": {
24
25
  "@owlmeans/context": "^0.1.18-rc.32",
25
- "@owlmeans/error": "^0.1.18-rc.35"
26
+ "@owlmeans/error": "^0.1.18-rc.36"
26
27
  },
27
28
  "peerDependencies": {
28
29
  "ajv": "*"
29
30
  },
30
31
  "devDependencies": {
31
32
  "@owlmeans/dep-config": "workspace:*",
33
+ "@types/bun": "^1.4.0",
32
34
  "nodemon": "^3.1.14",
33
35
  "typescript": "^7.0.2"
34
36
  },
package/src/errors.ts CHANGED
@@ -1,6 +1,12 @@
1
1
 
2
2
  import { ResilientError } from '@owlmeans/error'
3
3
 
4
+ /**
5
+ * The storage refusal family. A class that answers the caller's request declares the HTTP status
6
+ * an `@owlmeans/server-api` boundary answers it with (`static httpStatus`, inherited through the
7
+ * constructor chain): an absent record 404, a taken id or unique key 409. A fault — a misshaped
8
+ * record, an unsupported argument, a failed migration — declares nothing and answers 500.
9
+ */
4
10
  export class ResourceError extends ResilientError {
5
11
  public static override typeName = 'ResourceError'
6
12
 
@@ -9,8 +15,11 @@ export class ResourceError extends ResilientError {
9
15
  }
10
16
  }
11
17
 
18
+ /** The addressed record does not exist (`get`, `update`, `take` of an absent id or criteria). */
12
19
  export class UnknownRecordError extends ResourceError {
13
20
  public static override typeName = `${ResourceError.typeName}UnknownRecordError`
21
+ /** The addressed record is absent: answered 404. */
22
+ public static httpStatus = 404
14
23
 
15
24
  public static readonly idSeparator: string = '/'
16
25
 
@@ -19,8 +28,11 @@ export class UnknownRecordError extends ResourceError {
19
28
  this.type = UnknownRecordError.typeName
20
29
  }
21
30
 
31
+ /** Everything after the first separator — an id or a criteria description may contain one. */
22
32
  get id(): string {
23
- return this.message.split(UnknownRecordError.idSeparator)[1]
33
+ const at = this.message.indexOf(UnknownRecordError.idSeparator)
34
+
35
+ return at < 0 ? '' : this.message.slice(at + UnknownRecordError.idSeparator.length)
24
36
  }
25
37
  }
26
38
 
@@ -33,8 +45,11 @@ export class MisshapedRecord extends ResourceError {
33
45
  }
34
46
  }
35
47
 
48
+ /** A record under that id or unique key is already stored (`create`, a unique-index violation). */
36
49
  export class RecordExists extends ResourceError {
37
50
  public static override typeName = `${ResourceError.typeName}RecordExists`
51
+ /** The id or unique key is taken: answered 409. */
52
+ public static httpStatus = 409
38
53
 
39
54
  constructor(msg: string) {
40
55
  super(`record-exists:${msg}`)
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=errors.spec.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.spec.d.ts","sourceRoot":"","sources":["errors.spec.ts"],"names":[],"mappings":""}
@@ -0,0 +1,51 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+ import { ResilientError } from '@owlmeans/error';
3
+ import * as errors from '../src/errors.js';
4
+ /**
5
+ * The status an HTTP boundary (`@owlmeans/server-api` `errorStatus`) reads off a class: a static
6
+ * `httpStatus` inherited through the constructor chain. A refusal of the caller's request declares
7
+ * a 4xx; a fault declares nothing and answers 500.
8
+ */
9
+ const statusOf = (error) => error.constructor.httpStatus;
10
+ const DECLARED = {
11
+ ResourceError: undefined,
12
+ UnknownRecordError: 404,
13
+ RecordExists: 409,
14
+ MisshapedRecord: undefined,
15
+ RecordUpdateFailed: undefined,
16
+ UnsupportedArgumentError: undefined,
17
+ UnsupportedMethodError: undefined,
18
+ MigrationError: undefined,
19
+ MigrationConflict: undefined,
20
+ };
21
+ const classes = Object.entries(errors).filter(([, value]) => typeof value === 'function' && value.prototype instanceof ResilientError);
22
+ describe('resource errors — declared HTTP statuses', () => {
23
+ test('every class of the family has decided its status', () => {
24
+ expect(classes.map(([name]) => name).sort()).toEqual(Object.keys(DECLARED).sort());
25
+ });
26
+ test('an absent record is 404, a taken id or key 409; faults declare nothing', () => {
27
+ for (const [name, Class] of classes) {
28
+ expect([name, statusOf(new Class('x'))]).toEqual([name, DECLARED[name]]);
29
+ }
30
+ });
31
+ test('an error rebuilt from its marshalled form keeps its class and its status', () => {
32
+ for (const [name, Class] of classes) {
33
+ const back = ResilientError.ensure(new Class('x').marshal());
34
+ expect([name, back.constructor === Class, statusOf(back)]).toEqual([name, true, DECLARED[name]]);
35
+ }
36
+ });
37
+ });
38
+ describe('UnknownRecordError.id', () => {
39
+ test('is everything after the first separator, however many the id carries', () => {
40
+ expect(new errors.UnknownRecordError('abc').id).toBe('abc');
41
+ expect(new errors.UnknownRecordError('shelf/3/hammer').id).toBe('shelf/3/hammer');
42
+ expect(new errors.UnknownRecordError('{"url":"https://example.org/a"}').id)
43
+ .toBe('{"url":"https://example.org/a"}');
44
+ });
45
+ test('survives a marshal round trip', () => {
46
+ const back = ResilientError.ensure(new errors.UnknownRecordError('shelf/3').marshal());
47
+ expect(back).toBeInstanceOf(errors.UnknownRecordError);
48
+ expect(back.id).toBe('shelf/3');
49
+ });
50
+ });
51
+ //# sourceMappingURL=errors.spec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.spec.js","sourceRoot":"","sources":["errors.spec.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,60 @@
1
+ import { describe, expect, test } from 'bun:test'
2
+ import { ResilientError } from '@owlmeans/error'
3
+ import * as errors from '../src/errors.js'
4
+
5
+ /**
6
+ * The status an HTTP boundary (`@owlmeans/server-api` `errorStatus`) reads off a class: a static
7
+ * `httpStatus` inherited through the constructor chain. A refusal of the caller's request declares
8
+ * a 4xx; a fault declares nothing and answers 500.
9
+ */
10
+ const statusOf = (error: Error): unknown => (error.constructor as { httpStatus?: unknown }).httpStatus
11
+
12
+ const DECLARED: Record<string, number | undefined> = {
13
+ ResourceError: undefined,
14
+ UnknownRecordError: 404,
15
+ RecordExists: 409,
16
+ MisshapedRecord: undefined,
17
+ RecordUpdateFailed: undefined,
18
+ UnsupportedArgumentError: undefined,
19
+ UnsupportedMethodError: undefined,
20
+ MigrationError: undefined,
21
+ MigrationConflict: undefined,
22
+ }
23
+
24
+ const classes = Object.entries(errors).filter(([, value]) =>
25
+ typeof value === 'function' && value.prototype instanceof ResilientError
26
+ ) as [string, new (message: string) => ResilientError][]
27
+
28
+ describe('resource errors — declared HTTP statuses', () => {
29
+ test('every class of the family has decided its status', () => {
30
+ expect(classes.map(([name]) => name).sort()).toEqual(Object.keys(DECLARED).sort())
31
+ })
32
+
33
+ test('an absent record is 404, a taken id or key 409; faults declare nothing', () => {
34
+ for (const [name, Class] of classes) {
35
+ expect([name, statusOf(new Class('x'))]).toEqual([name, DECLARED[name]])
36
+ }
37
+ })
38
+
39
+ test('an error rebuilt from its marshalled form keeps its class and its status', () => {
40
+ for (const [name, Class] of classes) {
41
+ const back = ResilientError.ensure(new Class('x').marshal())
42
+ expect([name, back.constructor === Class, statusOf(back)]).toEqual([name, true, DECLARED[name]])
43
+ }
44
+ })
45
+ })
46
+
47
+ describe('UnknownRecordError.id', () => {
48
+ test('is everything after the first separator, however many the id carries', () => {
49
+ expect(new errors.UnknownRecordError('abc').id).toBe('abc')
50
+ expect(new errors.UnknownRecordError('shelf/3/hammer').id).toBe('shelf/3/hammer')
51
+ expect(new errors.UnknownRecordError('{"url":"https://example.org/a"}').id)
52
+ .toBe('{"url":"https://example.org/a"}')
53
+ })
54
+
55
+ test('survives a marshal round trip', () => {
56
+ const back = ResilientError.ensure(new errors.UnknownRecordError('shelf/3').marshal())
57
+ expect(back).toBeInstanceOf(errors.UnknownRecordError)
58
+ expect((back as errors.UnknownRecordError).id).toBe('shelf/3')
59
+ })
60
+ })
@@ -0,0 +1,17 @@
1
+ {
2
+ "extends": [
3
+ "@owlmeans/dep-config/tsconfig.base.json",
4
+ "@owlmeans/dep-config/tsconfig.node.json"
5
+ ],
6
+ "compilerOptions": {
7
+ "types": [
8
+ "bun"
9
+ ],
10
+ "rootDir": "../",
11
+ "noEmit": true
12
+ },
13
+ "include": [
14
+ "./**/*",
15
+ "../src/**/*"
16
+ ]
17
+ }
package/tsconfig.json CHANGED
@@ -6,5 +6,5 @@
6
6
  "rootDir": "./src/",
7
7
  "outDir": "./build/"
8
8
  },
9
- "exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
9
+ "exclude": ["./dist/**/*", "./build/**/*", "./tests/**/*", "./*.ts"]
10
10
  }