@roostorg/types 1.0.49
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/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@roostorg/types",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "1.0.49",
|
|
5
|
+
"description": "Shared types across Cove services",
|
|
6
|
+
"module": "transpiled/index.js",
|
|
7
|
+
"typings": "./transpiled/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc",
|
|
10
|
+
"prepublishOnly": "npm run build",
|
|
11
|
+
"test": "npm run build && node --test transpiled/test_scripts/"
|
|
12
|
+
},
|
|
13
|
+
"author": "",
|
|
14
|
+
"files": [
|
|
15
|
+
"transpiled"
|
|
16
|
+
],
|
|
17
|
+
"license": "ISC",
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@types/node": "^20.3.1",
|
|
20
|
+
"typescript": "^4.9.3"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"import": "./transpiled/index.js",
|
|
28
|
+
"types": "./transpiled/index.d.ts"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"date-fns": "^2.29.3",
|
|
33
|
+
"type-fest": "^4.3.2"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { type Opaque } from 'type-fest';
|
|
2
|
+
type Satisfies<T extends U, U> = T;
|
|
3
|
+
export declare const ScalarTypes: {
|
|
4
|
+
USER_ID: "USER_ID";
|
|
5
|
+
ID: "ID";
|
|
6
|
+
STRING: "STRING";
|
|
7
|
+
BOOLEAN: "BOOLEAN";
|
|
8
|
+
NUMBER: "NUMBER";
|
|
9
|
+
AUDIO: "AUDIO";
|
|
10
|
+
IMAGE: "IMAGE";
|
|
11
|
+
VIDEO: "VIDEO";
|
|
12
|
+
DATETIME: "DATETIME";
|
|
13
|
+
GEOHASH: "GEOHASH";
|
|
14
|
+
RELATED_ITEM: "RELATED_ITEM";
|
|
15
|
+
URL: "URL";
|
|
16
|
+
POLICY_ID: "POLICY_ID";
|
|
17
|
+
};
|
|
18
|
+
export type ScalarTypes = typeof ScalarTypes;
|
|
19
|
+
export type ScalarType = keyof typeof ScalarTypes;
|
|
20
|
+
export declare const ContainerTypes: {
|
|
21
|
+
ARRAY: "ARRAY";
|
|
22
|
+
MAP: "MAP";
|
|
23
|
+
};
|
|
24
|
+
export type ContainerTypes = typeof ContainerTypes;
|
|
25
|
+
export type ContainerType = keyof ContainerTypes;
|
|
26
|
+
type ScalarTypeRuntimeTypeMapping = Satisfies<{
|
|
27
|
+
[ScalarTypes.STRING]: string;
|
|
28
|
+
[ScalarTypes.ID]: string;
|
|
29
|
+
[ScalarTypes.USER_ID]: ItemIdentifier;
|
|
30
|
+
[ScalarTypes.GEOHASH]: string;
|
|
31
|
+
[ScalarTypes.URL]: UrlString;
|
|
32
|
+
[ScalarTypes.BOOLEAN]: boolean;
|
|
33
|
+
[ScalarTypes.NUMBER]: number;
|
|
34
|
+
[ScalarTypes.DATETIME]: DateString;
|
|
35
|
+
[ScalarTypes.AUDIO]: {
|
|
36
|
+
url: string;
|
|
37
|
+
};
|
|
38
|
+
[ScalarTypes.IMAGE]: {
|
|
39
|
+
url: string;
|
|
40
|
+
};
|
|
41
|
+
[ScalarTypes.VIDEO]: {
|
|
42
|
+
url: string;
|
|
43
|
+
};
|
|
44
|
+
[ScalarTypes.RELATED_ITEM]: RelatedItem;
|
|
45
|
+
[ScalarTypes.POLICY_ID]: string;
|
|
46
|
+
}, {
|
|
47
|
+
[K in ScalarType]: unknown;
|
|
48
|
+
}>;
|
|
49
|
+
type ContainerTypeRuntimeTypeMapping<ValueType extends ScalarType = ScalarType> = Satisfies<{
|
|
50
|
+
[ContainerTypes.ARRAY]: ScalarTypeRuntimeType<ValueType>[];
|
|
51
|
+
[ContainerTypes.MAP]: {
|
|
52
|
+
[key: string]: ScalarTypeRuntimeType<ValueType>;
|
|
53
|
+
};
|
|
54
|
+
}, {
|
|
55
|
+
[K in ContainerType]: unknown;
|
|
56
|
+
}>;
|
|
57
|
+
export type ScalarTypeRuntimeType<T extends ScalarType = ScalarType> = ScalarTypeRuntimeTypeMapping[T];
|
|
58
|
+
export type FieldType = ScalarType | ContainerType;
|
|
59
|
+
export type Container<T extends ContainerType> = Readonly<{
|
|
60
|
+
containerType: T;
|
|
61
|
+
keyScalarType: Satisfies<{
|
|
62
|
+
[ContainerTypes.MAP]: ScalarType;
|
|
63
|
+
[ContainerTypes.ARRAY]: null;
|
|
64
|
+
}, {
|
|
65
|
+
[K in ContainerType]: unknown;
|
|
66
|
+
}>[T];
|
|
67
|
+
valueScalarType: ScalarType;
|
|
68
|
+
}>;
|
|
69
|
+
export type Field<T extends FieldType = FieldType> = {
|
|
70
|
+
[Type in ScalarType]: {
|
|
71
|
+
name: string;
|
|
72
|
+
type: Type;
|
|
73
|
+
required: boolean;
|
|
74
|
+
container: null;
|
|
75
|
+
};
|
|
76
|
+
}[ScalarType & T] | {
|
|
77
|
+
[Type in ContainerType]: {
|
|
78
|
+
name: string;
|
|
79
|
+
type: Type;
|
|
80
|
+
required: boolean;
|
|
81
|
+
container: Container<Type>;
|
|
82
|
+
};
|
|
83
|
+
}[ContainerType & T];
|
|
84
|
+
export type ContainerTypeRuntimeType<T extends ContainerType, V extends ScalarType = ScalarType> = ContainerTypeRuntimeTypeMapping<V>[T];
|
|
85
|
+
export type FieldTypeRuntimeType<T extends FieldType, ContainerValueType extends ScalarType = ScalarType> = ScalarTypeRuntimeType<T & ScalarType> | ContainerTypeRuntimeType<T & ContainerType, ContainerValueType>;
|
|
86
|
+
/**
|
|
87
|
+
* In the case of scalar fields, this returns the ScalarType of their single
|
|
88
|
+
* value; in the case of container fields, it gives the type of the scalars in
|
|
89
|
+
* the container (i.e., the ScalarType for the array's items or map's values).
|
|
90
|
+
* With container fields, we don't track details at the type level of what
|
|
91
|
+
* scalars they contain, so this type assumes it could be anything.
|
|
92
|
+
*/
|
|
93
|
+
export type FieldScalarType<T extends FieldType> = T extends ScalarType ? T : ScalarType;
|
|
94
|
+
/**
|
|
95
|
+
* A TaggedScalar holds a scalar value, along with a label identifying its
|
|
96
|
+
* ScalarTypes. This label is necessary because not all scalar values have a
|
|
97
|
+
* unique js runtime representation (e.g., ScalarTypes.STRING and
|
|
98
|
+
* ScalarTypes.GEOHASH are both represented as strings), so, without the label,
|
|
99
|
+
* we wouldn't know unambiguously which ScalarType a value belongs to, which we
|
|
100
|
+
* need sometimes (e.g., when deciding whether we can pass it to a signal).
|
|
101
|
+
*
|
|
102
|
+
* For why this type is written this way, see
|
|
103
|
+
* https://github.com/protegoapi/protego/pull/20#discussion_r883974513
|
|
104
|
+
*/
|
|
105
|
+
type EnumScalarType = ScalarTypes['STRING'] | ScalarTypes['NUMBER'];
|
|
106
|
+
export type TaggedScalar<T extends ScalarType> = {
|
|
107
|
+
[K in ScalarType]: {
|
|
108
|
+
type: K;
|
|
109
|
+
value: ScalarTypeRuntimeType<K>;
|
|
110
|
+
} | (K extends EnumScalarType ? {
|
|
111
|
+
type: K;
|
|
112
|
+
value: ScalarTypeRuntimeType<K>;
|
|
113
|
+
enum: readonly ScalarTypeRuntimeType<K>[];
|
|
114
|
+
ordered: boolean;
|
|
115
|
+
} : never);
|
|
116
|
+
}[T];
|
|
117
|
+
export declare function isContainerField(it: Field): it is Field<ContainerType>;
|
|
118
|
+
export declare function isContainerType(it: FieldType): it is ContainerType;
|
|
119
|
+
export declare function getScalarType<T extends FieldType>(it: Field<T>): FieldScalarType<T>;
|
|
120
|
+
export declare function isMediaType(it: ScalarType): boolean;
|
|
121
|
+
export declare function isMediaValue<T extends ScalarType>(it: TaggedScalar<T>): it is TaggedScalar<T & (ScalarTypes['IMAGE'] | ScalarTypes['VIDEO'] | ScalarTypes['AUDIO'])>;
|
|
122
|
+
/**
|
|
123
|
+
* Takes an array of strings and returns an object with a property for each
|
|
124
|
+
* string in the array, where the string is used as both the name and value for
|
|
125
|
+
* the property.
|
|
126
|
+
*
|
|
127
|
+
* This is useful to get type safety and automatic refactoring in some cases.
|
|
128
|
+
* E.g., imagine you're setting the default value for a field on a Sequelize
|
|
129
|
+
* model. Let's say the field can have three legal values: 'a', 'b', or 'c'.
|
|
130
|
+
* So, you'll initialize the model with some config object for the field, like
|
|
131
|
+
* `{ defaultValue: 'a' }`. Now, the type that this `defaultValue` key expects
|
|
132
|
+
* will be very vague -- likely `string` or maybe even `any` -- because it was
|
|
133
|
+
* defined by Sequelize and doesn't know about your field's specific legal
|
|
134
|
+
* values. Therefore, you could write `{ defaultValue: 'invalid' }` and TS
|
|
135
|
+
* wouldn't complain; moreover, even if you wrote `{ defaultValue: 'a' }`, which
|
|
136
|
+
* would be correct at the time, a rename on the value 'a' would not
|
|
137
|
+
* automatically rename the value here, because they're not linked by type.
|
|
138
|
+
*
|
|
139
|
+
* To fix these issues, it can be very helpful to have an object like
|
|
140
|
+
* `const legalValues = { 'a': 'a', 'b': 'b', 'c': 'c' };` because, then,
|
|
141
|
+
* you can do `{ defaultValues: legalValues.a }`, and you're guaranteed a typo-
|
|
142
|
+
* free and rename-friendly value. That `legalValues` object is what this
|
|
143
|
+
* function makes.
|
|
144
|
+
*
|
|
145
|
+
* Obviously, such an object is similar to a TS enum (hence this function's
|
|
146
|
+
* name). The key difference, though, is that the values in this object are
|
|
147
|
+
* typed as string literals, whereas the value for each case in an enum is
|
|
148
|
+
* treated by the type system as intentionally opaque. So, having that
|
|
149
|
+
* visibility in an 'enum-like' can help a lot with assignability, when the
|
|
150
|
+
* source value is a string literal type (rather than the source having to have
|
|
151
|
+
* been constructed with the same enum).
|
|
152
|
+
*
|
|
153
|
+
* We also exploit this for assigning values that come in from GraphQL. The
|
|
154
|
+
* GraphQL value is an enum; we want to use a different type in our inner layers
|
|
155
|
+
* (which shouldn't be coupled to GraphQL); but, if our internal type were an
|
|
156
|
+
* enum, the GraphQL enum wouldn't be assignable to it (even if their runtime
|
|
157
|
+
* values match). However, if the internal type is an "enum like", then TS will
|
|
158
|
+
* allow GraphQL enum to be assignable to it iff the enum's runtime values are
|
|
159
|
+
* legal values in the enum like.
|
|
160
|
+
*/
|
|
161
|
+
export declare function makeEnumLike<T extends string>(strings: readonly T[]): { [K in T]: K; };
|
|
162
|
+
export declare const hiveProjectNames: readonly ["Visual", "Text", "Speech", "OCR", "Demographic"];
|
|
163
|
+
export type HiveProjectName = (typeof hiveProjectNames)[number];
|
|
164
|
+
export declare function isHiveProjectName(name: string): name is HiveProjectName;
|
|
165
|
+
export type SignalSubcategory = {
|
|
166
|
+
id: string;
|
|
167
|
+
label: string;
|
|
168
|
+
description?: string;
|
|
169
|
+
children: SignalSubcategory[];
|
|
170
|
+
};
|
|
171
|
+
export type DateString = Opaque<string, 'DateString'>;
|
|
172
|
+
export declare function parseDateString(it: DateString): Date;
|
|
173
|
+
/**
|
|
174
|
+
* Returns a DateString if the input string can be parsed
|
|
175
|
+
* as a date; else undefined. Accepts strings in a rather limited set
|
|
176
|
+
* of formats for now. (See {@link parseJSON} docs for details.)
|
|
177
|
+
*/
|
|
178
|
+
export declare function makeDateString(it: string): DateString | undefined;
|
|
179
|
+
export type RelatedItem = Satisfies<{
|
|
180
|
+
id: string;
|
|
181
|
+
typeId: string;
|
|
182
|
+
name?: string;
|
|
183
|
+
}, ItemIdentifier>;
|
|
184
|
+
/**
|
|
185
|
+
* UrlString represents a string that's known to be parsable into a valid URL;
|
|
186
|
+
* analogous to DateString.
|
|
187
|
+
*/
|
|
188
|
+
export type UrlString = Opaque<string, 'UrlString'>;
|
|
189
|
+
export type ItemIdentifier = Readonly<{
|
|
190
|
+
id: string;
|
|
191
|
+
typeId: string;
|
|
192
|
+
}>;
|
|
193
|
+
export declare const ItemTypeKind: {
|
|
194
|
+
CONTENT: "CONTENT";
|
|
195
|
+
THREAD: "THREAD";
|
|
196
|
+
USER: "USER";
|
|
197
|
+
};
|
|
198
|
+
export type ItemTypeKind = keyof typeof ItemTypeKind;
|
|
199
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { isValid, parseJSON } from 'date-fns';
|
|
2
|
+
// TODO: Representing geographical _points_ and _areas_ with the same scalar
|
|
3
|
+
// type is probably not ideal.
|
|
4
|
+
//
|
|
5
|
+
// NB: the ID type refers to ids for any type of entity, but USER_ID should be
|
|
6
|
+
// used instead for fields that hold ids of users protego might know about. E.g.,
|
|
7
|
+
// imagine a `Message` content type. Both the `to` and `from` fields could hold
|
|
8
|
+
// ScalarTypes.USER_IDs, and then a rule could flag the message if _either_ the
|
|
9
|
+
// sender or the recipient satisfies some condition (after passing the user id
|
|
10
|
+
// to a user-related signal).
|
|
11
|
+
export const ScalarTypes = makeEnumLike([
|
|
12
|
+
'USER_ID',
|
|
13
|
+
'ID',
|
|
14
|
+
'STRING',
|
|
15
|
+
'BOOLEAN',
|
|
16
|
+
'NUMBER',
|
|
17
|
+
'AUDIO',
|
|
18
|
+
'IMAGE',
|
|
19
|
+
'VIDEO',
|
|
20
|
+
'DATETIME',
|
|
21
|
+
'GEOHASH',
|
|
22
|
+
'RELATED_ITEM',
|
|
23
|
+
'URL',
|
|
24
|
+
'POLICY_ID',
|
|
25
|
+
]);
|
|
26
|
+
export const ContainerTypes = makeEnumLike(['ARRAY', 'MAP']);
|
|
27
|
+
const containerTypes = new Set(Object.values(ContainerTypes));
|
|
28
|
+
export function isContainerField(it) {
|
|
29
|
+
return isContainerType(it.type);
|
|
30
|
+
}
|
|
31
|
+
export function isContainerType(it) {
|
|
32
|
+
return containerTypes.has(it);
|
|
33
|
+
}
|
|
34
|
+
export function getScalarType(it) {
|
|
35
|
+
return (isContainerField(it) ? it.container.valueScalarType : it.type);
|
|
36
|
+
}
|
|
37
|
+
export function isMediaType(it) {
|
|
38
|
+
return (it === ScalarTypes.AUDIO ||
|
|
39
|
+
it === ScalarTypes.VIDEO ||
|
|
40
|
+
it === ScalarTypes.IMAGE);
|
|
41
|
+
}
|
|
42
|
+
export function isMediaValue(it) {
|
|
43
|
+
return isMediaType(it.type);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Takes an array of strings and returns an object with a property for each
|
|
47
|
+
* string in the array, where the string is used as both the name and value for
|
|
48
|
+
* the property.
|
|
49
|
+
*
|
|
50
|
+
* This is useful to get type safety and automatic refactoring in some cases.
|
|
51
|
+
* E.g., imagine you're setting the default value for a field on a Sequelize
|
|
52
|
+
* model. Let's say the field can have three legal values: 'a', 'b', or 'c'.
|
|
53
|
+
* So, you'll initialize the model with some config object for the field, like
|
|
54
|
+
* `{ defaultValue: 'a' }`. Now, the type that this `defaultValue` key expects
|
|
55
|
+
* will be very vague -- likely `string` or maybe even `any` -- because it was
|
|
56
|
+
* defined by Sequelize and doesn't know about your field's specific legal
|
|
57
|
+
* values. Therefore, you could write `{ defaultValue: 'invalid' }` and TS
|
|
58
|
+
* wouldn't complain; moreover, even if you wrote `{ defaultValue: 'a' }`, which
|
|
59
|
+
* would be correct at the time, a rename on the value 'a' would not
|
|
60
|
+
* automatically rename the value here, because they're not linked by type.
|
|
61
|
+
*
|
|
62
|
+
* To fix these issues, it can be very helpful to have an object like
|
|
63
|
+
* `const legalValues = { 'a': 'a', 'b': 'b', 'c': 'c' };` because, then,
|
|
64
|
+
* you can do `{ defaultValues: legalValues.a }`, and you're guaranteed a typo-
|
|
65
|
+
* free and rename-friendly value. That `legalValues` object is what this
|
|
66
|
+
* function makes.
|
|
67
|
+
*
|
|
68
|
+
* Obviously, such an object is similar to a TS enum (hence this function's
|
|
69
|
+
* name). The key difference, though, is that the values in this object are
|
|
70
|
+
* typed as string literals, whereas the value for each case in an enum is
|
|
71
|
+
* treated by the type system as intentionally opaque. So, having that
|
|
72
|
+
* visibility in an 'enum-like' can help a lot with assignability, when the
|
|
73
|
+
* source value is a string literal type (rather than the source having to have
|
|
74
|
+
* been constructed with the same enum).
|
|
75
|
+
*
|
|
76
|
+
* We also exploit this for assigning values that come in from GraphQL. The
|
|
77
|
+
* GraphQL value is an enum; we want to use a different type in our inner layers
|
|
78
|
+
* (which shouldn't be coupled to GraphQL); but, if our internal type were an
|
|
79
|
+
* enum, the GraphQL enum wouldn't be assignable to it (even if their runtime
|
|
80
|
+
* values match). However, if the internal type is an "enum like", then TS will
|
|
81
|
+
* allow GraphQL enum to be assignable to it iff the enum's runtime values are
|
|
82
|
+
* legal values in the enum like.
|
|
83
|
+
*/
|
|
84
|
+
export function makeEnumLike(strings) {
|
|
85
|
+
return Object.fromEntries(strings.map((it) => [it, it]));
|
|
86
|
+
}
|
|
87
|
+
export const hiveProjectNames = [
|
|
88
|
+
'Visual',
|
|
89
|
+
'Text',
|
|
90
|
+
'Speech',
|
|
91
|
+
'OCR',
|
|
92
|
+
'Demographic',
|
|
93
|
+
];
|
|
94
|
+
export function isHiveProjectName(name) {
|
|
95
|
+
return hiveProjectNames.includes(name);
|
|
96
|
+
}
|
|
97
|
+
export function parseDateString(it) {
|
|
98
|
+
return new Date(it);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Returns a DateString if the input string can be parsed
|
|
102
|
+
* as a date; else undefined. Accepts strings in a rather limited set
|
|
103
|
+
* of formats for now. (See {@link parseJSON} docs for details.)
|
|
104
|
+
*/
|
|
105
|
+
export function makeDateString(it) {
|
|
106
|
+
const potentialDate = parseJSON(it);
|
|
107
|
+
// check if the parsing succeeded; return accordingly
|
|
108
|
+
return isValid(potentialDate)
|
|
109
|
+
? potentialDate.toISOString()
|
|
110
|
+
: undefined;
|
|
111
|
+
}
|
|
112
|
+
export const ItemTypeKind = makeEnumLike(['CONTENT', 'THREAD', 'USER']);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// We allow the test files, and only the test files, to reference node types cuz
|
|
2
|
+
// we don't run these in the browser.
|
|
3
|
+
/// <reference types="node" />
|
|
4
|
+
import * as assert from 'node:assert';
|
|
5
|
+
import { describe, it } from 'node:test';
|
|
6
|
+
import { makeDateString } from '../index.js';
|
|
7
|
+
const stringsAndExpectedResults = {
|
|
8
|
+
'': undefined,
|
|
9
|
+
abc: undefined,
|
|
10
|
+
'1': undefined,
|
|
11
|
+
'2023-04-12T19:47:09.406Z': '2023-04-12T19:47:09.406Z',
|
|
12
|
+
'2023.0.1.01': undefined,
|
|
13
|
+
'2023.01.01': undefined,
|
|
14
|
+
'2023-04-12T19:47:09.40604Z': '2023-04-12T19:47:09.406Z',
|
|
15
|
+
'2023-04-12T19:47:09.4Z': '2023-04-12T19:47:09.400Z',
|
|
16
|
+
};
|
|
17
|
+
describe('makeDateString', () => {
|
|
18
|
+
it('should properly handle inputs', () => {
|
|
19
|
+
Object.entries(stringsAndExpectedResults).map(([key, value]) => {
|
|
20
|
+
const result = makeDateString(key);
|
|
21
|
+
assert.ok(result === value, `makeDateString('${key}') was expected to be ${value}, but instead was ${result}.`);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
});
|