@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,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
+ });