rastack 0.0.16 → 0.0.20
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/CHANGELOG.md +43876 -43013
- package/dist/.prettierrc +7 -7
- package/dist/compile/analyze.js +19 -11
- package/dist/compile/index.js +26 -17
- package/dist/compile/manifest.js +2 -3
- package/dist/compile/openapi.js +5 -8
- package/dist/compile/program.js +19 -10
- package/dist/csv-schema.js +5 -6
- package/dist/define/index.js +2 -2
- package/dist/entity-generation/delete-method.js +8 -10
- package/dist/entity-generation/form.js +80 -82
- package/dist/entity-generation/get-method.js +35 -36
- package/dist/entity-generation/imports.js +24 -20
- package/dist/entity-generation/sync-method.d.ts +1 -1
- package/dist/entity-generation/sync-method.js +45 -46
- package/dist/entity-generation/update-method.js +9 -10
- package/dist/rad-wasm-build.d.ts +1 -1
- package/dist/rad-wasm-build.js +15 -6
- package/dist/rad.js +9 -2
- package/dist/schema/camel-to-pastel.js +1 -2
- package/dist/schema/capitalise-first-letter.js +1 -2
- package/dist/schema/extract-response.js +3 -4
- package/dist/schema/fetch-schema.js +4 -16
- package/dist/schema/remove-non-model-paths.js +1 -2
- package/dist/schema/to-camel-case.js +1 -2
- package/dist/schema/to-pastel-case.js +1 -2
- package/dist/schema-convert.js +22 -33
- package/dist/schema-entities.js +177 -189
- package/dist/schema-fetch.js +46 -63
- package/dist/schema-full.js +23 -36
- package/dist/schema-index.js +16 -27
- package/dist/schema-params.js +11 -22
- package/dist/seed.js +95 -97
- package/hooks/form/form.ts +207 -207
- package/hooks/form/index.ts +8 -8
- package/hooks/form/interfaces.ts +217 -217
- package/hooks/form/structure.ts +39 -39
- package/hooks/form/validate-schema.ts +49 -49
- package/hooks/index.ts +3 -3
- package/hooks/query/api.ts +42 -42
- package/hooks/query/delete.ts +45 -45
- package/hooks/query/fetch.ts +48 -48
- package/hooks/query/index.ts +21 -21
- package/hooks/query/interfaces.ts +111 -111
- package/hooks/query/list.ts +286 -286
- package/hooks/query/update.ts +88 -88
- package/hooks/query/url.ts +31 -31
- package/hooks/real-time/index.ts +1 -1
- package/hooks/real-time/pusher.ts +43 -43
- package/jest.config.cjs +6 -6
- package/package.json +57 -57
- package/provider/index.ts +13 -13
- package/provider/provider.tsx +187 -187
- package/provider/types.ts +114 -114
- package/provider/warehouse.ts +167 -167
- package/provider/wasm.ts +124 -124
- package/runtime.ts +3 -3
- package/src/.prettierrc +7 -7
- package/src/compile/analyze.ts +224 -224
- package/src/compile/index.ts +86 -86
- package/src/compile/manifest.ts +10 -10
- package/src/compile/model.ts +69 -69
- package/src/compile/openapi.ts +266 -266
- package/src/compile/program.ts +40 -40
- package/src/csv-schema.ts +187 -187
- package/src/define/index.ts +162 -162
- package/src/entity-generation/delete-method.ts +37 -37
- package/src/entity-generation/form.ts +169 -169
- package/src/entity-generation/get-method.ts +80 -80
- package/src/entity-generation/imports.ts +110 -110
- package/src/entity-generation/sync-method.ts +222 -222
- package/src/entity-generation/update-method.ts +38 -38
- package/src/index.ts +77 -77
- package/src/rad-compile.ts +88 -88
- package/src/rad-wasm-build.ts +102 -102
- package/src/rad.ts +101 -101
- package/src/scan.ts +89 -89
- package/src/schema/camel-to-pastel.ts +3 -3
- package/src/schema/capitalise-first-letter.ts +3 -3
- package/src/schema/extract-response.ts +38 -38
- package/src/schema/fetch-schema.ts +12 -12
- package/src/schema/remove-non-model-paths.ts +13 -13
- package/src/schema/to-camel-case.ts +7 -7
- package/src/schema/to-pastel-case.ts +6 -6
- package/src/schema-convert.ts +61 -61
- package/src/schema-entities.ts +401 -401
- package/src/schema-fetch.ts +82 -82
- package/src/schema-full.ts +38 -38
- package/src/schema-index.ts +29 -29
- package/src/schema-params.ts +116 -116
- package/src/seed.ts +297 -297
- package/sync/engine.ts +392 -392
- package/sync/hooks.ts +450 -450
- package/sync/index.ts +8 -8
- package/sync/persistence.ts +237 -237
- package/sync/provider.tsx +91 -91
- package/sync/registry.ts +36 -36
- package/sync/store.ts +126 -126
- package/sync/transactions.ts +300 -300
- package/sync/types.ts +94 -94
- package/test/compile.spec.ts +192 -192
- package/test/csv-schema.spec.ts +143 -143
- package/test/schema-entities.spec.ts +688 -688
- package/tsconfig.json +24 -24
- package/types.ts +18 -18
package/test/csv-schema.spec.ts
CHANGED
|
@@ -1,143 +1,143 @@
|
|
|
1
|
-
import path from "path";
|
|
2
|
-
import os from "os";
|
|
3
|
-
import fs from "fs";
|
|
4
|
-
import { parseCsv, inferSchema, discoverCsvFiles } from "../src/csv-schema";
|
|
5
|
-
|
|
6
|
-
const REPO_ROOT = path.join(__dirname, "..");
|
|
7
|
-
|
|
8
|
-
function tmpCsv(content: string): string {
|
|
9
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rad-test-"));
|
|
10
|
-
const file = path.join(dir, "test.csv");
|
|
11
|
-
fs.writeFileSync(file, content);
|
|
12
|
-
return file;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe("parseCsv", () => {
|
|
16
|
-
it("parses headers and rows", () => {
|
|
17
|
-
const { headers, rows } = parseCsv("id,name,age\n1,Alice,30\n2,Bob,25");
|
|
18
|
-
expect(headers).toEqual(["id", "name", "age"]);
|
|
19
|
-
expect(rows).toHaveLength(2);
|
|
20
|
-
expect(rows[0]).toEqual(["1", "Alice", "30"]);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("handles quoted fields with commas", () => {
|
|
24
|
-
const { headers, rows } = parseCsv(`name,bio\nAlice,"Engineer, Pilot"\nBob,Writer`);
|
|
25
|
-
expect(rows[0][1]).toBe("Engineer, Pilot");
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it("returns empty for blank input", () => {
|
|
29
|
-
const { headers, rows } = parseCsv("");
|
|
30
|
-
expect(headers).toHaveLength(0);
|
|
31
|
-
expect(rows).toHaveLength(0);
|
|
32
|
-
});
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
describe("inferSchema — type inference", () => {
|
|
36
|
-
it("infers integer columns", () => {
|
|
37
|
-
const csv = tmpCsv("id,score\n1,100\n2,200\n3,300");
|
|
38
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
39
|
-
const id = schema.columns.find((c) => c.name === "id")!;
|
|
40
|
-
const score = schema.columns.find((c) => c.name === "score")!;
|
|
41
|
-
expect(id.type).toBe("integer");
|
|
42
|
-
expect(score.type).toBe("integer");
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it("infers float columns", () => {
|
|
46
|
-
const csv = tmpCsv("price\n1.99\n3.50\n0.01");
|
|
47
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
48
|
-
expect(schema.columns[0].type).toBe("float");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("infers boolean columns", () => {
|
|
52
|
-
const csv = tmpCsv("active\ntrue\nfalse\ntrue");
|
|
53
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
54
|
-
expect(schema.columns[0].type).toBe("boolean");
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
it("infers date columns", () => {
|
|
58
|
-
const csv = tmpCsv("dob\n1990-01-01\n1985-06-15");
|
|
59
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
60
|
-
// dob matches PII but type should still be date
|
|
61
|
-
expect(schema.columns[0].type).toBe("date");
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
it("infers datetime columns", () => {
|
|
65
|
-
const csv = tmpCsv("created_at\n2024-01-01T10:00:00Z\n2024-02-01T12:30:00Z");
|
|
66
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
67
|
-
expect(schema.columns[0].type).toBe("datetime");
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it("marks id column as primaryKey", () => {
|
|
71
|
-
const csv = tmpCsv("id,name\n1,Alice");
|
|
72
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
73
|
-
const id = schema.columns.find((c) => c.name === "id")!;
|
|
74
|
-
expect(id.primaryKey).toBe(true);
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
it("detects nullable columns", () => {
|
|
78
|
-
const csv = tmpCsv("id,note\n1,hello\n2,\n3,world");
|
|
79
|
-
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
80
|
-
const note = schema.columns.find((c) => c.name === "note")!;
|
|
81
|
-
expect(note.nullable).toBe(true);
|
|
82
|
-
});
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
describe("inferSchema — PII detection", () => {
|
|
86
|
-
it("flags passport_no as PII", () => {
|
|
87
|
-
const csv = tmpCsv("id,passport_no\n1,AB123456\n2,CD789012");
|
|
88
|
-
const { schema, piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
89
|
-
const col = schema.columns.find((c) => c.name === "passport_no")!;
|
|
90
|
-
expect(col.pii).toBe(true);
|
|
91
|
-
expect(piiViolations).toHaveLength(1);
|
|
92
|
-
expect(piiViolations[0].column).toBe("passport_no");
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("flags email as PII", () => {
|
|
96
|
-
const csv = tmpCsv("id,email\n1,alice@example.com");
|
|
97
|
-
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
98
|
-
expect(piiViolations.some((v) => v.column === "email")).toBe(true);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it("flags dob as PII", () => {
|
|
102
|
-
const csv = tmpCsv("id,dob\n1,1990-01-01");
|
|
103
|
-
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
104
|
-
expect(piiViolations.some((v) => v.column === "dob")).toBe(true);
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
it("does not flag non-PII columns", () => {
|
|
108
|
-
const csv = tmpCsv("id,flight_code,origin,destination\n1,SA101,JNB,LHR");
|
|
109
|
-
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
110
|
-
expect(piiViolations).toHaveLength(0);
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("generates a stable fingerprint", () => {
|
|
114
|
-
const csv = tmpCsv("id,name\n1,Alice\n2,Bob");
|
|
115
|
-
const { schema: a } = inferSchema(csv, REPO_ROOT);
|
|
116
|
-
const { schema: b } = inferSchema(csv, REPO_ROOT);
|
|
117
|
-
expect(a.fingerprint).toBe(b.fingerprint);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("generates different fingerprints for different schemas", () => {
|
|
121
|
-
const csvA = tmpCsv("id,name\n1,Alice");
|
|
122
|
-
const csvB = tmpCsv("id,name,email\n1,Alice,a@b.com");
|
|
123
|
-
const { schema: a } = inferSchema(csvA, REPO_ROOT);
|
|
124
|
-
const { schema: b } = inferSchema(csvB, REPO_ROOT);
|
|
125
|
-
expect(a.fingerprint).not.toBe(b.fingerprint);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
describe("discoverCsvFiles", () => {
|
|
130
|
-
it("finds all csv files in a directory", () => {
|
|
131
|
-
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rad-test-"));
|
|
132
|
-
fs.writeFileSync(path.join(dir, "a.csv"), "id\n1");
|
|
133
|
-
fs.writeFileSync(path.join(dir, "b.csv"), "id\n2");
|
|
134
|
-
fs.writeFileSync(path.join(dir, "readme.txt"), "not a csv");
|
|
135
|
-
const files = discoverCsvFiles(dir);
|
|
136
|
-
expect(files).toHaveLength(2);
|
|
137
|
-
expect(files.every((f) => f.endsWith(".csv"))).toBe(true);
|
|
138
|
-
});
|
|
139
|
-
|
|
140
|
-
it("returns empty for missing directory", () => {
|
|
141
|
-
expect(discoverCsvFiles("/nonexistent/path")).toHaveLength(0);
|
|
142
|
-
});
|
|
143
|
-
});
|
|
1
|
+
import path from "path";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import { parseCsv, inferSchema, discoverCsvFiles } from "../src/csv-schema";
|
|
5
|
+
|
|
6
|
+
const REPO_ROOT = path.join(__dirname, "..");
|
|
7
|
+
|
|
8
|
+
function tmpCsv(content: string): string {
|
|
9
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rad-test-"));
|
|
10
|
+
const file = path.join(dir, "test.csv");
|
|
11
|
+
fs.writeFileSync(file, content);
|
|
12
|
+
return file;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("parseCsv", () => {
|
|
16
|
+
it("parses headers and rows", () => {
|
|
17
|
+
const { headers, rows } = parseCsv("id,name,age\n1,Alice,30\n2,Bob,25");
|
|
18
|
+
expect(headers).toEqual(["id", "name", "age"]);
|
|
19
|
+
expect(rows).toHaveLength(2);
|
|
20
|
+
expect(rows[0]).toEqual(["1", "Alice", "30"]);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("handles quoted fields with commas", () => {
|
|
24
|
+
const { headers, rows } = parseCsv(`name,bio\nAlice,"Engineer, Pilot"\nBob,Writer`);
|
|
25
|
+
expect(rows[0][1]).toBe("Engineer, Pilot");
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it("returns empty for blank input", () => {
|
|
29
|
+
const { headers, rows } = parseCsv("");
|
|
30
|
+
expect(headers).toHaveLength(0);
|
|
31
|
+
expect(rows).toHaveLength(0);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("inferSchema — type inference", () => {
|
|
36
|
+
it("infers integer columns", () => {
|
|
37
|
+
const csv = tmpCsv("id,score\n1,100\n2,200\n3,300");
|
|
38
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
39
|
+
const id = schema.columns.find((c) => c.name === "id")!;
|
|
40
|
+
const score = schema.columns.find((c) => c.name === "score")!;
|
|
41
|
+
expect(id.type).toBe("integer");
|
|
42
|
+
expect(score.type).toBe("integer");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("infers float columns", () => {
|
|
46
|
+
const csv = tmpCsv("price\n1.99\n3.50\n0.01");
|
|
47
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
48
|
+
expect(schema.columns[0].type).toBe("float");
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("infers boolean columns", () => {
|
|
52
|
+
const csv = tmpCsv("active\ntrue\nfalse\ntrue");
|
|
53
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
54
|
+
expect(schema.columns[0].type).toBe("boolean");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("infers date columns", () => {
|
|
58
|
+
const csv = tmpCsv("dob\n1990-01-01\n1985-06-15");
|
|
59
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
60
|
+
// dob matches PII but type should still be date
|
|
61
|
+
expect(schema.columns[0].type).toBe("date");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("infers datetime columns", () => {
|
|
65
|
+
const csv = tmpCsv("created_at\n2024-01-01T10:00:00Z\n2024-02-01T12:30:00Z");
|
|
66
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
67
|
+
expect(schema.columns[0].type).toBe("datetime");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("marks id column as primaryKey", () => {
|
|
71
|
+
const csv = tmpCsv("id,name\n1,Alice");
|
|
72
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
73
|
+
const id = schema.columns.find((c) => c.name === "id")!;
|
|
74
|
+
expect(id.primaryKey).toBe(true);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("detects nullable columns", () => {
|
|
78
|
+
const csv = tmpCsv("id,note\n1,hello\n2,\n3,world");
|
|
79
|
+
const { schema } = inferSchema(csv, REPO_ROOT);
|
|
80
|
+
const note = schema.columns.find((c) => c.name === "note")!;
|
|
81
|
+
expect(note.nullable).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe("inferSchema — PII detection", () => {
|
|
86
|
+
it("flags passport_no as PII", () => {
|
|
87
|
+
const csv = tmpCsv("id,passport_no\n1,AB123456\n2,CD789012");
|
|
88
|
+
const { schema, piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
89
|
+
const col = schema.columns.find((c) => c.name === "passport_no")!;
|
|
90
|
+
expect(col.pii).toBe(true);
|
|
91
|
+
expect(piiViolations).toHaveLength(1);
|
|
92
|
+
expect(piiViolations[0].column).toBe("passport_no");
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("flags email as PII", () => {
|
|
96
|
+
const csv = tmpCsv("id,email\n1,alice@example.com");
|
|
97
|
+
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
98
|
+
expect(piiViolations.some((v) => v.column === "email")).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("flags dob as PII", () => {
|
|
102
|
+
const csv = tmpCsv("id,dob\n1,1990-01-01");
|
|
103
|
+
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
104
|
+
expect(piiViolations.some((v) => v.column === "dob")).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("does not flag non-PII columns", () => {
|
|
108
|
+
const csv = tmpCsv("id,flight_code,origin,destination\n1,SA101,JNB,LHR");
|
|
109
|
+
const { piiViolations } = inferSchema(csv, REPO_ROOT);
|
|
110
|
+
expect(piiViolations).toHaveLength(0);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("generates a stable fingerprint", () => {
|
|
114
|
+
const csv = tmpCsv("id,name\n1,Alice\n2,Bob");
|
|
115
|
+
const { schema: a } = inferSchema(csv, REPO_ROOT);
|
|
116
|
+
const { schema: b } = inferSchema(csv, REPO_ROOT);
|
|
117
|
+
expect(a.fingerprint).toBe(b.fingerprint);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("generates different fingerprints for different schemas", () => {
|
|
121
|
+
const csvA = tmpCsv("id,name\n1,Alice");
|
|
122
|
+
const csvB = tmpCsv("id,name,email\n1,Alice,a@b.com");
|
|
123
|
+
const { schema: a } = inferSchema(csvA, REPO_ROOT);
|
|
124
|
+
const { schema: b } = inferSchema(csvB, REPO_ROOT);
|
|
125
|
+
expect(a.fingerprint).not.toBe(b.fingerprint);
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe("discoverCsvFiles", () => {
|
|
130
|
+
it("finds all csv files in a directory", () => {
|
|
131
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "rad-test-"));
|
|
132
|
+
fs.writeFileSync(path.join(dir, "a.csv"), "id\n1");
|
|
133
|
+
fs.writeFileSync(path.join(dir, "b.csv"), "id\n2");
|
|
134
|
+
fs.writeFileSync(path.join(dir, "readme.txt"), "not a csv");
|
|
135
|
+
const files = discoverCsvFiles(dir);
|
|
136
|
+
expect(files).toHaveLength(2);
|
|
137
|
+
expect(files.every((f) => f.endsWith(".csv"))).toBe(true);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("returns empty for missing directory", () => {
|
|
141
|
+
expect(discoverCsvFiles("/nonexistent/path")).toHaveLength(0);
|
|
142
|
+
});
|
|
143
|
+
});
|