@pgpm/types 0.12.0 → 0.13.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/README.md +12 -6
- package/__tests__/domains.pgutils.test.ts +56 -9
- package/__tests__/domains.test.ts +56 -9
- package/deploy/schemas/public/domains/attachment.sql +1 -6
- package/deploy/schemas/public/domains/origin.sql +2 -4
- package/deploy/schemas/public/domains/upload.sql +5 -1
- package/package.json +3 -3
- package/pgpm.plan +1 -0
- package/revert/schemas/public/domains/origin.sql +1 -0
- package/sql/launchql-types--0.9.0.sql +6 -3
- package/verify/schemas/public/domains/origin.sql +1 -0
package/README.md
CHANGED
|
@@ -22,9 +22,10 @@ Core PostgreSQL data types with SQL scripts.
|
|
|
22
22
|
|
|
23
23
|
- **email**: Case-insensitive email address validation
|
|
24
24
|
- **url**: HTTP/HTTPS URL validation
|
|
25
|
+
- **origin**: Origin URL validation (scheme + host)
|
|
25
26
|
- **hostname**: Domain name validation
|
|
26
27
|
- **image**: JSON-based image metadata with URL and MIME type
|
|
27
|
-
- **attachment**:
|
|
28
|
+
- **attachment**: File attachment metadata as a URL string or JSON with URL and MIME type
|
|
28
29
|
- **upload**: File upload metadata
|
|
29
30
|
- **single_select**: Single selection field
|
|
30
31
|
- **multiple_select**: Multiple selection field
|
|
@@ -156,7 +157,8 @@ INSERT INTO customers (domain) VALUES
|
|
|
156
157
|
|
|
157
158
|
### Image and Attachment Domains
|
|
158
159
|
|
|
159
|
-
The `image`
|
|
160
|
+
The `image` domain stores JSON objects with URL and MIME type information. The `attachment` domain accepts either that JSON shape or a plain URL string.
|
|
161
|
+
The `upload` domain uses the same JSON object shape as `image`, ensuring both the file URL and MIME type are present.
|
|
160
162
|
|
|
161
163
|
```sql
|
|
162
164
|
-- Valid image
|
|
@@ -166,9 +168,12 @@ INSERT INTO customers (profile_image) VALUES
|
|
|
166
168
|
-- Valid attachment
|
|
167
169
|
INSERT INTO customers (document) VALUES
|
|
168
170
|
('{"url": "https://storage.example.com/file.pdf", "mime": "application/pdf"}'::json);
|
|
171
|
+
|
|
172
|
+
-- Valid attachment as plain URL
|
|
173
|
+
INSERT INTO customers (document) VALUES ('https://storage.example.com/favicon.ico');
|
|
169
174
|
```
|
|
170
175
|
|
|
171
|
-
**Structure**:
|
|
176
|
+
**Structure**: Image values and JSON-form attachments expect `url` and `mime` properties; attachments also allow a bare URL string.
|
|
172
177
|
|
|
173
178
|
## Domain Types Reference
|
|
174
179
|
|
|
@@ -176,10 +181,11 @@ INSERT INTO customers (document) VALUES
|
|
|
176
181
|
|--------|-----------|-------------|---------|
|
|
177
182
|
| `email` | `citext` | Case-insensitive email address | `user@example.com` |
|
|
178
183
|
| `url` | `text` | HTTP/HTTPS URL | `https://example.com/path` |
|
|
184
|
+
| `origin` | `text` | Origin (scheme + host) | `https://example.com` |
|
|
179
185
|
| `hostname` | `text` | Domain name without protocol | `example.com` |
|
|
180
186
|
| `image` | `json` | Image metadata with URL and MIME | `{"url": "...", "mime": "image/jpeg"}` |
|
|
181
|
-
| `attachment` | `json` | File attachment metadata | `{"url": "...", "mime": "application/pdf"}` |
|
|
182
|
-
| `upload` | `
|
|
187
|
+
| `attachment` | `json` | File attachment URL or metadata | `{"url": "...", "mime": "application/pdf"}` or `https://example.com/favicon.ico` |
|
|
188
|
+
| `upload` | `json` | File upload metadata (URL + MIME) | `{"url": "...", "mime": "application/pdf"}` |
|
|
183
189
|
| `single_select` | `text` | Single selection value | Text value |
|
|
184
190
|
| `multiple_select` | `text[]` | Multiple selection values | Array of text values |
|
|
185
191
|
|
|
@@ -207,7 +213,7 @@ The test suite validates:
|
|
|
207
213
|
- Email format validation (valid and invalid cases)
|
|
208
214
|
- URL format validation with extensive test cases
|
|
209
215
|
- Hostname format validation
|
|
210
|
-
- Image and attachment JSON structure validation
|
|
216
|
+
- Image, upload, and attachment JSON structure validation
|
|
211
217
|
|
|
212
218
|
## Related Tooling
|
|
213
219
|
|
|
@@ -44,15 +44,37 @@ const invalidUrls = [
|
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
const validAttachments = [
|
|
47
|
+
'http://www.foo.bar/some.jpg',
|
|
48
|
+
'https://foo.bar/some.PNG'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const invalidAttachments = [
|
|
52
|
+
'hi there',
|
|
53
|
+
'ftp://foo.bar/some.png',
|
|
54
|
+
'https:///foo.bar/some.png'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const validImages = [
|
|
47
58
|
{ url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' },
|
|
48
59
|
{ url: 'https://foo.bar/some.PNG', mime: 'image/jpg' }
|
|
49
60
|
];
|
|
50
61
|
|
|
51
|
-
const
|
|
62
|
+
const invalidImages = [
|
|
52
63
|
{ url: 'hi there' },
|
|
53
64
|
{ url: 'https://foo.bar/some.png' }
|
|
54
65
|
];
|
|
55
66
|
|
|
67
|
+
const validUploads = [
|
|
68
|
+
{ url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' },
|
|
69
|
+
{ url: 'https://foo.bar/some.PNG', mime: 'image/png' }
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const invalidUploads = [
|
|
73
|
+
{ url: 'hi there' },
|
|
74
|
+
{ url: 'https://foo.bar/some.png' },
|
|
75
|
+
{ url: 'ftp://foo.bar/some.png', mime: 'image/png' }
|
|
76
|
+
];
|
|
77
|
+
|
|
56
78
|
let pg: PgTestClient;
|
|
57
79
|
let teardown: () => Promise<void>;
|
|
58
80
|
|
|
@@ -68,7 +90,8 @@ CREATE TABLE customers (
|
|
|
68
90
|
image image,
|
|
69
91
|
attachment attachment,
|
|
70
92
|
domain hostname,
|
|
71
|
-
email email
|
|
93
|
+
email email,
|
|
94
|
+
upload upload
|
|
72
95
|
);
|
|
73
96
|
`);
|
|
74
97
|
});
|
|
@@ -87,24 +110,48 @@ afterAll(async () => {
|
|
|
87
110
|
|
|
88
111
|
describe('types', () => {
|
|
89
112
|
it('valid attachment and image', async () => {
|
|
90
|
-
for (const
|
|
91
|
-
await pg.any(`INSERT INTO customers (
|
|
92
|
-
|
|
113
|
+
for (const attachment of validAttachments) {
|
|
114
|
+
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [attachment]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const image of validImages) {
|
|
118
|
+
await pg.any(`INSERT INTO customers (image) VALUES ($1::json);`, [image]);
|
|
93
119
|
}
|
|
94
120
|
});
|
|
95
121
|
|
|
96
122
|
it('invalid attachment and image', async () => {
|
|
97
|
-
for (const
|
|
123
|
+
for (const attachment of invalidAttachments) {
|
|
98
124
|
let failed = false;
|
|
99
125
|
try {
|
|
100
|
-
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [
|
|
126
|
+
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [attachment]);
|
|
101
127
|
} catch (e) {
|
|
102
128
|
failed = true;
|
|
103
129
|
}
|
|
104
130
|
expect(failed).toBe(true);
|
|
105
|
-
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const image of invalidImages) {
|
|
134
|
+
let failed = false;
|
|
135
|
+
try {
|
|
136
|
+
await pg.any(`INSERT INTO customers (image) VALUES ($1::json);`, [image]);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
failed = true;
|
|
139
|
+
}
|
|
140
|
+
expect(failed).toBe(true);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('valid upload', async () => {
|
|
145
|
+
for (const upload of validUploads) {
|
|
146
|
+
await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('invalid upload', async () => {
|
|
151
|
+
for (const upload of invalidUploads) {
|
|
152
|
+
let failed = false;
|
|
106
153
|
try {
|
|
107
|
-
await pg.any(`INSERT INTO customers (
|
|
154
|
+
await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]);
|
|
108
155
|
} catch (e) {
|
|
109
156
|
failed = true;
|
|
110
157
|
}
|
|
@@ -44,15 +44,37 @@ const invalidUrls = [
|
|
|
44
44
|
];
|
|
45
45
|
|
|
46
46
|
const validAttachments = [
|
|
47
|
+
'http://www.foo.bar/some.jpg',
|
|
48
|
+
'https://foo.bar/some.PNG'
|
|
49
|
+
];
|
|
50
|
+
|
|
51
|
+
const invalidAttachments = [
|
|
52
|
+
'hi there',
|
|
53
|
+
'ftp://foo.bar/some.png',
|
|
54
|
+
'https:///foo.bar/some.png'
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
const validImages = [
|
|
47
58
|
{ url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' },
|
|
48
59
|
{ url: 'https://foo.bar/some.PNG', mime: 'image/jpg' }
|
|
49
60
|
];
|
|
50
61
|
|
|
51
|
-
const
|
|
62
|
+
const invalidImages = [
|
|
52
63
|
{ url: 'hi there' },
|
|
53
64
|
{ url: 'https://foo.bar/some.png' }
|
|
54
65
|
];
|
|
55
66
|
|
|
67
|
+
const validUploads = [
|
|
68
|
+
{ url: 'http://www.foo.bar/some.jpg', mime: 'image/jpg' },
|
|
69
|
+
{ url: 'https://foo.bar/some.PNG', mime: 'image/png' }
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
const invalidUploads = [
|
|
73
|
+
{ url: 'hi there' },
|
|
74
|
+
{ url: 'https://foo.bar/some.png' },
|
|
75
|
+
{ url: 'ftp://foo.bar/some.png', mime: 'image/png' }
|
|
76
|
+
];
|
|
77
|
+
|
|
56
78
|
let pg: PgTestClient;
|
|
57
79
|
let teardown: () => Promise<void>;
|
|
58
80
|
|
|
@@ -68,7 +90,8 @@ CREATE TABLE customers (
|
|
|
68
90
|
image image,
|
|
69
91
|
attachment attachment,
|
|
70
92
|
domain hostname,
|
|
71
|
-
email email
|
|
93
|
+
email email,
|
|
94
|
+
upload upload
|
|
72
95
|
);
|
|
73
96
|
`);
|
|
74
97
|
});
|
|
@@ -87,24 +110,48 @@ afterAll(async () => {
|
|
|
87
110
|
|
|
88
111
|
describe('types', () => {
|
|
89
112
|
it('valid attachment and image', async () => {
|
|
90
|
-
for (const
|
|
91
|
-
await pg.any(`INSERT INTO customers (
|
|
92
|
-
|
|
113
|
+
for (const attachment of validAttachments) {
|
|
114
|
+
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [attachment]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const image of validImages) {
|
|
118
|
+
await pg.any(`INSERT INTO customers (image) VALUES ($1::json);`, [image]);
|
|
93
119
|
}
|
|
94
120
|
});
|
|
95
121
|
|
|
96
122
|
it('invalid attachment and image', async () => {
|
|
97
|
-
for (const
|
|
123
|
+
for (const attachment of invalidAttachments) {
|
|
98
124
|
let failed = false;
|
|
99
125
|
try {
|
|
100
|
-
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [
|
|
126
|
+
await pg.any(`INSERT INTO customers (attachment) VALUES ($1);`, [attachment]);
|
|
101
127
|
} catch (e) {
|
|
102
128
|
failed = true;
|
|
103
129
|
}
|
|
104
130
|
expect(failed).toBe(true);
|
|
105
|
-
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
for (const image of invalidImages) {
|
|
134
|
+
let failed = false;
|
|
135
|
+
try {
|
|
136
|
+
await pg.any(`INSERT INTO customers (image) VALUES ($1::json);`, [image]);
|
|
137
|
+
} catch (e) {
|
|
138
|
+
failed = true;
|
|
139
|
+
}
|
|
140
|
+
expect(failed).toBe(true);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('valid upload', async () => {
|
|
145
|
+
for (const upload of validUploads) {
|
|
146
|
+
await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('invalid upload', async () => {
|
|
151
|
+
for (const upload of invalidUploads) {
|
|
152
|
+
let failed = false;
|
|
106
153
|
try {
|
|
107
|
-
await pg.any(`INSERT INTO customers (
|
|
154
|
+
await pg.any(`INSERT INTO customers (upload) VALUES ($1::json);`, [upload]);
|
|
108
155
|
} catch (e) {
|
|
109
156
|
failed = true;
|
|
110
157
|
}
|
|
@@ -2,11 +2,6 @@
|
|
|
2
2
|
-- requires: schemas/public/schema
|
|
3
3
|
|
|
4
4
|
BEGIN;
|
|
5
|
-
CREATE DOMAIN attachment AS
|
|
6
|
-
value ?& ARRAY['url', 'mime']
|
|
7
|
-
AND
|
|
8
|
-
value->>'url' ~ '^(https?)://[^\s/$.?#].[^\s]*$'
|
|
9
|
-
);
|
|
5
|
+
CREATE DOMAIN attachment AS text CHECK (VALUE ~ '^(https?)://[^\s/$.?#].[^\s]*$');
|
|
10
6
|
COMMENT ON DOMAIN attachment IS E'@name launchqlInternalTypeAttachment';
|
|
11
7
|
COMMIT;
|
|
12
|
-
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
-- Deploy schemas/public/domains/origin to pg
|
|
2
|
-
|
|
3
2
|
-- requires: schemas/public/schema
|
|
4
3
|
|
|
5
4
|
BEGIN;
|
|
6
|
-
|
|
7
|
-
CREATE DOMAIN origin AS text CHECK (VALUE = substring(VALUE from '^(https?://[^/]*)') );
|
|
5
|
+
CREATE DOMAIN origin AS text CHECK (VALUE = substring(VALUE from '^(https?://[^/]*)'));
|
|
8
6
|
COMMENT ON DOMAIN origin IS E'@name launchqlInternalTypeOrigin';
|
|
9
|
-
|
|
10
7
|
COMMIT;
|
|
8
|
+
|
|
@@ -4,7 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
BEGIN;
|
|
6
6
|
|
|
7
|
-
CREATE DOMAIN upload AS
|
|
7
|
+
CREATE DOMAIN upload AS jsonb CHECK (
|
|
8
|
+
value ?& ARRAY['url', 'mime']
|
|
9
|
+
AND
|
|
10
|
+
value->>'url' ~ '^(https?)://[^\s/$.?#].[^\s]*$'
|
|
11
|
+
);
|
|
8
12
|
COMMENT ON DOMAIN upload IS E'@name launchqlInternalTypeUpload';
|
|
9
13
|
|
|
10
14
|
COMMIT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pgpm/types",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Core PostgreSQL data types with deploy/verify/revert SQL scripts",
|
|
5
5
|
"author": "Dan Lynch <pyramation@gmail.com>",
|
|
6
6
|
"contributors": [
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"test:watch": "jest --watch"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@pgpm/verify": "0.
|
|
24
|
+
"@pgpm/verify": "0.13.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"pgpm": "^0.2.0"
|
|
@@ -34,5 +34,5 @@
|
|
|
34
34
|
"bugs": {
|
|
35
35
|
"url": "https://github.com/launchql/pgpm-modules/issues"
|
|
36
36
|
},
|
|
37
|
-
"gitHead": "
|
|
37
|
+
"gitHead": "e7de11be3a2fd806929adf48acc556d812c75aa6"
|
|
38
38
|
}
|
package/pgpm.plan
CHANGED
|
@@ -8,6 +8,7 @@ schemas/public/domains/email [schemas/public/schema] 2017-08-11T08:11:51Z skitch
|
|
|
8
8
|
schemas/public/domains/hostname [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/hostname
|
|
9
9
|
schemas/public/domains/image [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/image
|
|
10
10
|
schemas/public/domains/multiple_select [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/multiple_select
|
|
11
|
+
schemas/public/domains/origin [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/origin
|
|
11
12
|
schemas/public/domains/single_select [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/single_select
|
|
12
13
|
schemas/public/domains/upload [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/upload
|
|
13
14
|
schemas/public/domains/url [schemas/public/schema] 2017-08-11T08:11:51Z skitch <skitch@5b0c196eeb62> # add schemas/public/domains/url
|
|
@@ -35,12 +35,15 @@ CREATE DOMAIN single_select AS jsonb
|
|
|
35
35
|
|
|
36
36
|
COMMENT ON DOMAIN single_select IS '@name launchqlInternalTypeSingleSelect';
|
|
37
37
|
|
|
38
|
-
CREATE DOMAIN upload AS
|
|
39
|
-
CHECK (
|
|
38
|
+
CREATE DOMAIN upload AS jsonb
|
|
39
|
+
CHECK (
|
|
40
|
+
value ?& ARRAY['url', 'mime']
|
|
41
|
+
AND (value ->> 'url') ~ E'^(https?)://[^\\s/$.?#].[^\\s]*$'
|
|
42
|
+
);
|
|
40
43
|
|
|
41
44
|
COMMENT ON DOMAIN upload IS '@name launchqlInternalTypeUpload';
|
|
42
45
|
|
|
43
46
|
CREATE DOMAIN url AS text
|
|
44
47
|
CHECK (value ~ E'^(https?)://[^\\s/$.?#].[^\\s]*$');
|
|
45
48
|
|
|
46
|
-
COMMENT ON DOMAIN url IS '@name launchqlInternalTypeUrl';
|
|
49
|
+
COMMENT ON DOMAIN url IS '@name launchqlInternalTypeUrl';
|