rastack 0.0.16
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 +43013 -0
- package/dist/.prettierrc +7 -0
- package/dist/compile/analyze.d.ts +12 -0
- package/dist/compile/analyze.js +233 -0
- package/dist/compile/index.d.ts +19 -0
- package/dist/compile/index.js +107 -0
- package/dist/compile/manifest.d.ts +4 -0
- package/dist/compile/manifest.js +12 -0
- package/dist/compile/model.d.ts +54 -0
- package/dist/compile/model.js +7 -0
- package/dist/compile/openapi.d.ts +11 -0
- package/dist/compile/openapi.js +233 -0
- package/dist/compile/program.d.ts +10 -0
- package/dist/compile/program.js +61 -0
- package/dist/csv-schema.d.ts +28 -0
- package/dist/csv-schema.js +162 -0
- package/dist/define/index.d.ts +117 -0
- package/dist/define/index.js +74 -0
- package/dist/entity-generation/delete-method.d.ts +1 -0
- package/dist/entity-generation/delete-method.js +31 -0
- package/dist/entity-generation/form.d.ts +13 -0
- package/dist/entity-generation/form.js +136 -0
- package/dist/entity-generation/get-method.d.ts +1 -0
- package/dist/entity-generation/get-method.js +67 -0
- package/dist/entity-generation/imports.d.ts +5 -0
- package/dist/entity-generation/imports.js +91 -0
- package/dist/entity-generation/sync-method.d.ts +30 -0
- package/dist/entity-generation/sync-method.js +158 -0
- package/dist/entity-generation/update-method.d.ts +1 -0
- package/dist/entity-generation/update-method.js +29 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +53 -0
- package/dist/rad-compile.d.ts +11 -0
- package/dist/rad-compile.js +82 -0
- package/dist/rad-wasm-build.d.ts +14 -0
- package/dist/rad-wasm-build.js +74 -0
- package/dist/rad.d.ts +17 -0
- package/dist/rad.js +88 -0
- package/dist/scan.d.ts +12 -0
- package/dist/scan.js +79 -0
- package/dist/schema/camel-to-pastel.d.ts +1 -0
- package/dist/schema/camel-to-pastel.js +7 -0
- package/dist/schema/capitalise-first-letter.d.ts +1 -0
- package/dist/schema/capitalise-first-letter.js +7 -0
- package/dist/schema/extract-response.d.ts +2 -0
- package/dist/schema/extract-response.js +30 -0
- package/dist/schema/fetch-schema.d.ts +7 -0
- package/dist/schema/fetch-schema.js +23 -0
- package/dist/schema/remove-non-model-paths.d.ts +2 -0
- package/dist/schema/remove-non-model-paths.js +15 -0
- package/dist/schema/to-camel-case.d.ts +1 -0
- package/dist/schema/to-camel-case.js +11 -0
- package/dist/schema/to-pastel-case.d.ts +1 -0
- package/dist/schema/to-pastel-case.js +10 -0
- package/dist/schema-convert.d.ts +1 -0
- package/dist/schema-convert.js +63 -0
- package/dist/schema-entities.d.ts +14 -0
- package/dist/schema-entities.js +293 -0
- package/dist/schema-fetch.d.ts +1 -0
- package/dist/schema-fetch.js +81 -0
- package/dist/schema-full.d.ts +1 -0
- package/dist/schema-full.js +45 -0
- package/dist/schema-index.d.ts +1 -0
- package/dist/schema-index.js +34 -0
- package/dist/schema-params.d.ts +1 -0
- package/dist/schema-params.js +100 -0
- package/dist/seed.d.ts +17 -0
- package/dist/seed.js +252 -0
- package/hooks/form/form.ts +207 -0
- package/hooks/form/index.ts +8 -0
- package/hooks/form/interfaces.ts +217 -0
- package/hooks/form/structure.ts +39 -0
- package/hooks/form/validate-schema.ts +49 -0
- package/hooks/index.ts +3 -0
- package/hooks/query/api.ts +42 -0
- package/hooks/query/delete.ts +45 -0
- package/hooks/query/fetch.ts +48 -0
- package/hooks/query/index.ts +21 -0
- package/hooks/query/interfaces.ts +111 -0
- package/hooks/query/list.ts +286 -0
- package/hooks/query/update.ts +88 -0
- package/hooks/query/url.ts +31 -0
- package/hooks/real-time/index.ts +1 -0
- package/hooks/real-time/pusher.ts +43 -0
- package/jest.config.cjs +6 -0
- package/package.json +57 -0
- package/provider/index.ts +13 -0
- package/provider/provider.tsx +187 -0
- package/provider/types.ts +114 -0
- package/provider/warehouse.ts +167 -0
- package/provider/wasm.ts +124 -0
- package/runtime.ts +3 -0
- package/src/.prettierrc +7 -0
- package/src/compile/analyze.ts +224 -0
- package/src/compile/index.ts +86 -0
- package/src/compile/manifest.ts +10 -0
- package/src/compile/model.ts +69 -0
- package/src/compile/openapi.ts +266 -0
- package/src/compile/program.ts +40 -0
- package/src/csv-schema.ts +187 -0
- package/src/define/index.ts +162 -0
- package/src/entity-generation/delete-method.ts +37 -0
- package/src/entity-generation/form.ts +169 -0
- package/src/entity-generation/get-method.ts +80 -0
- package/src/entity-generation/imports.ts +110 -0
- package/src/entity-generation/sync-method.ts +222 -0
- package/src/entity-generation/update-method.ts +38 -0
- package/src/index.ts +77 -0
- package/src/rad-compile.ts +88 -0
- package/src/rad-wasm-build.ts +102 -0
- package/src/rad.ts +101 -0
- package/src/scan.ts +89 -0
- package/src/schema/camel-to-pastel.ts +3 -0
- package/src/schema/capitalise-first-letter.ts +3 -0
- package/src/schema/extract-response.ts +38 -0
- package/src/schema/fetch-schema.ts +12 -0
- package/src/schema/remove-non-model-paths.ts +13 -0
- package/src/schema/to-camel-case.ts +7 -0
- package/src/schema/to-pastel-case.ts +6 -0
- package/src/schema-convert.ts +61 -0
- package/src/schema-entities.ts +401 -0
- package/src/schema-fetch.ts +82 -0
- package/src/schema-full.ts +38 -0
- package/src/schema-index.ts +29 -0
- package/src/schema-params.ts +116 -0
- package/src/seed.ts +297 -0
- package/sync/engine.ts +392 -0
- package/sync/hooks.ts +450 -0
- package/sync/index.ts +8 -0
- package/sync/persistence.ts +237 -0
- package/sync/provider.tsx +91 -0
- package/sync/registry.ts +36 -0
- package/sync/store.ts +126 -0
- package/sync/transactions.ts +300 -0
- package/sync/types.ts +94 -0
- package/test/compile.spec.ts +192 -0
- package/test/csv-schema.spec.ts +143 -0
- package/test/schema-entities.spec.ts +688 -0
- package/tsconfig.json +24 -0
- package/types.ts +19 -0
package/dist/seed.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* rad seed
|
|
5
|
+
*
|
|
6
|
+
* Loads CSV files from a local directory or GCS bucket into the target
|
|
7
|
+
* Iceberg namespace. Performs schema inference, PII check, and idempotent
|
|
8
|
+
* table creation before writing rows.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* rad seed [--env dev|staging] [--source gs://bucket/ | --source ./myapp-dev/]
|
|
12
|
+
*
|
|
13
|
+
* Environment variables (set via CI secrets or .env):
|
|
14
|
+
* GCP_PROJECT GCP project ID
|
|
15
|
+
* ICEBERG_WAREHOUSE gs://... path to Iceberg warehouse root
|
|
16
|
+
* ICEBERG_CATALOG_URI sqlite:///... or http://... catalog URI
|
|
17
|
+
*/
|
|
18
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
19
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
20
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
21
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
22
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
23
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
24
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
25
|
+
});
|
|
26
|
+
};
|
|
27
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
28
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
29
|
+
};
|
|
30
|
+
var _a, _b;
|
|
31
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
32
|
+
const fs_1 = __importDefault(require("fs"));
|
|
33
|
+
const path_1 = __importDefault(require("path"));
|
|
34
|
+
const child_process_1 = require("child_process");
|
|
35
|
+
const csv_schema_1 = require("./csv-schema");
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Args
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const args = process.argv.slice(2);
|
|
40
|
+
function getArg(flag) {
|
|
41
|
+
const i = args.indexOf(flag);
|
|
42
|
+
return i !== -1 && args[i + 1] ? args[i + 1] : null;
|
|
43
|
+
}
|
|
44
|
+
const env = (_a = getArg("--env")) !== null && _a !== void 0 ? _a : "dev";
|
|
45
|
+
const source = (_b = getArg("--source")) !== null && _b !== void 0 ? _b : path_1.default.join(process.cwd(), "myapp-dev");
|
|
46
|
+
const repoRoot = process.cwd();
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Step 1: resolve CSV files
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
function resolveFiles() {
|
|
51
|
+
if (source.startsWith("gs://")) {
|
|
52
|
+
return downloadFromGcs(source);
|
|
53
|
+
}
|
|
54
|
+
return (0, csv_schema_1.discoverCsvFiles)(source);
|
|
55
|
+
}
|
|
56
|
+
function downloadFromGcs(gcsUri) {
|
|
57
|
+
const tmpDir = path_1.default.join(repoRoot, ".rad", "tmp", "seed");
|
|
58
|
+
fs_1.default.mkdirSync(tmpDir, { recursive: true });
|
|
59
|
+
console.log(`Downloading CSVs from ${gcsUri} ...`);
|
|
60
|
+
try {
|
|
61
|
+
(0, child_process_1.execSync)(`gcloud storage cp "${gcsUri}*.csv" "${tmpDir}/" --quiet`, {
|
|
62
|
+
stdio: "inherit",
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (_a) {
|
|
66
|
+
// gcloud storage cp uses gsutil-style globs — fall back
|
|
67
|
+
(0, child_process_1.execSync)(`gsutil -m cp "${gcsUri}*.csv" "${tmpDir}/"`, { stdio: "inherit" });
|
|
68
|
+
}
|
|
69
|
+
return (0, csv_schema_1.discoverCsvFiles)(tmpDir);
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Step 2: validate schemas (PII check — hard fail in CI)
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
function validateSchema(csvPath) {
|
|
75
|
+
const { schema, piiViolations } = (0, csv_schema_1.inferSchema)(csvPath, repoRoot);
|
|
76
|
+
if (piiViolations.length > 0) {
|
|
77
|
+
const list = piiViolations.map((v) => ` ${v.column}: ${v.reason}`).join("\n");
|
|
78
|
+
throw new Error(`PII detected in ${path_1.default.basename(csvPath)}:\n${list}\n` +
|
|
79
|
+
`Remove PII data before seeding. Run "rad scan" to review.`);
|
|
80
|
+
}
|
|
81
|
+
return schema;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Step 3: emit Iceberg DDL for a table
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function icebergType(col) {
|
|
87
|
+
switch (col) {
|
|
88
|
+
case "integer":
|
|
89
|
+
return "long";
|
|
90
|
+
case "float":
|
|
91
|
+
return "double";
|
|
92
|
+
case "boolean":
|
|
93
|
+
return "boolean";
|
|
94
|
+
case "date":
|
|
95
|
+
return "date";
|
|
96
|
+
case "datetime":
|
|
97
|
+
return "timestamp";
|
|
98
|
+
default:
|
|
99
|
+
return "string";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function buildCreateTableSql(schema, namespace) {
|
|
103
|
+
const cols = schema.columns
|
|
104
|
+
.map((c) => {
|
|
105
|
+
const type = icebergType(c.type);
|
|
106
|
+
const nullable = c.nullable ? "" : " NOT NULL";
|
|
107
|
+
return ` ${c.name} ${type}${nullable}`;
|
|
108
|
+
})
|
|
109
|
+
.join(",\n");
|
|
110
|
+
return (`CREATE TABLE IF NOT EXISTS ${namespace}.${schema.tableName} (\n` +
|
|
111
|
+
`${cols}\n` +
|
|
112
|
+
`) USING iceberg\n` +
|
|
113
|
+
`TBLPROPERTIES (\n` +
|
|
114
|
+
` 'write.format.default'='parquet',\n` +
|
|
115
|
+
` 'write.metadata.fingerprint'='${schema.fingerprint}'\n` +
|
|
116
|
+
`);`);
|
|
117
|
+
}
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
// Step 4: write schema fingerprints to registry
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
function writeFingerprint(schema) {
|
|
122
|
+
const registryDir = path_1.default.join(repoRoot, ".rad", "registry");
|
|
123
|
+
fs_1.default.mkdirSync(registryDir, { recursive: true });
|
|
124
|
+
const registryPath = path_1.default.join(registryDir, `${schema.tableName}.json`);
|
|
125
|
+
let existing = {};
|
|
126
|
+
if (fs_1.default.existsSync(registryPath)) {
|
|
127
|
+
existing = JSON.parse(fs_1.default.readFileSync(registryPath, "utf-8"));
|
|
128
|
+
}
|
|
129
|
+
const prev = existing.fingerprint;
|
|
130
|
+
const changed = prev && prev !== schema.fingerprint;
|
|
131
|
+
const record = {
|
|
132
|
+
tableName: schema.tableName,
|
|
133
|
+
fingerprint: schema.fingerprint,
|
|
134
|
+
columns: schema.columns,
|
|
135
|
+
rowCount: schema.rowCount,
|
|
136
|
+
seededAt: new Date().toISOString(),
|
|
137
|
+
env,
|
|
138
|
+
previousFingerprint: prev !== null && prev !== void 0 ? prev : null,
|
|
139
|
+
};
|
|
140
|
+
fs_1.default.writeFileSync(registryPath, JSON.stringify(record, null, 2));
|
|
141
|
+
if (changed) {
|
|
142
|
+
console.warn(` ⚠ Schema change detected for "${schema.tableName}": ` +
|
|
143
|
+
`${prev} → ${schema.fingerprint}. A migration may be required.`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
// ---------------------------------------------------------------------------
|
|
147
|
+
// Step 5: build a Python seed script and run it via django-iceberg
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
function buildPythonSeedScript(schemas, csvPaths) {
|
|
150
|
+
var _a, _b;
|
|
151
|
+
const namespace = env;
|
|
152
|
+
const warehouse = (_a = process.env.ICEBERG_WAREHOUSE) !== null && _a !== void 0 ? _a : "data/warehouse";
|
|
153
|
+
const catalogUri = (_b = process.env.ICEBERG_CATALOG_URI) !== null && _b !== void 0 ? _b : "sqlite:///data/catalog.db";
|
|
154
|
+
const tableBlocks = schemas
|
|
155
|
+
.map((schema, i) => {
|
|
156
|
+
const ddl = buildCreateTableSql(schema, namespace).replace(/`/g, "\\`");
|
|
157
|
+
const csvPathEscaped = csvPaths[i].replace(/\\/g, "/");
|
|
158
|
+
return `
|
|
159
|
+
# --- ${schema.tableName} ---
|
|
160
|
+
ddl = """${ddl}"""
|
|
161
|
+
cat.execute(ddl)
|
|
162
|
+
df = pl.read_csv("${csvPathEscaped}", infer_schema_length=1000)
|
|
163
|
+
tbl = cat.load_table("${namespace}.${schema.tableName}")
|
|
164
|
+
tbl.overwrite(df.to_arrow())
|
|
165
|
+
print(f" Seeded ${schema.tableName}: ${schema.rowCount} rows")
|
|
166
|
+
`;
|
|
167
|
+
})
|
|
168
|
+
.join("\n");
|
|
169
|
+
return `
|
|
170
|
+
import polars as pl
|
|
171
|
+
from pyiceberg.catalog import load_catalog
|
|
172
|
+
|
|
173
|
+
cat = load_catalog(
|
|
174
|
+
"default",
|
|
175
|
+
**{
|
|
176
|
+
"uri": "${catalogUri}",
|
|
177
|
+
"warehouse": "${warehouse}",
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
try:
|
|
182
|
+
cat.create_namespace("${namespace}")
|
|
183
|
+
except Exception:
|
|
184
|
+
pass # namespace already exists
|
|
185
|
+
|
|
186
|
+
${tableBlocks}
|
|
187
|
+
|
|
188
|
+
print("Seed complete.")
|
|
189
|
+
`.trim();
|
|
190
|
+
}
|
|
191
|
+
// ---------------------------------------------------------------------------
|
|
192
|
+
// Main
|
|
193
|
+
// ---------------------------------------------------------------------------
|
|
194
|
+
function main() {
|
|
195
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
196
|
+
var _a;
|
|
197
|
+
console.log(`\nrad seed env=${env} source=${source}\n`);
|
|
198
|
+
const files = resolveFiles();
|
|
199
|
+
if (files.length === 0) {
|
|
200
|
+
console.log("No CSV files found. Nothing to seed.");
|
|
201
|
+
process.exit(0);
|
|
202
|
+
}
|
|
203
|
+
console.log(`Found ${files.length} CSV file(s):\n`);
|
|
204
|
+
const schemas = [];
|
|
205
|
+
for (const csvPath of files) {
|
|
206
|
+
const rel = path_1.default.relative(repoRoot, csvPath);
|
|
207
|
+
process.stdout.write(` Validating ${rel} ... `);
|
|
208
|
+
try {
|
|
209
|
+
const schema = validateSchema(csvPath);
|
|
210
|
+
schemas.push(schema);
|
|
211
|
+
console.log(`OK [${schema.columns.length} cols, ${schema.rowCount} rows]`);
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
console.error(`FAIL\n${err.message}`);
|
|
215
|
+
process.exit(1);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Write fingerprints before seeding so CI can detect schema drift
|
|
219
|
+
for (const schema of schemas) {
|
|
220
|
+
writeFingerprint(schema);
|
|
221
|
+
}
|
|
222
|
+
// Emit DDL summary
|
|
223
|
+
const ddlDir = path_1.default.join(repoRoot, ".rad", "ddl");
|
|
224
|
+
fs_1.default.mkdirSync(ddlDir, { recursive: true });
|
|
225
|
+
for (const schema of schemas) {
|
|
226
|
+
const ddl = buildCreateTableSql(schema, env);
|
|
227
|
+
fs_1.default.writeFileSync(path_1.default.join(ddlDir, `${schema.tableName}.sql`), ddl + "\n");
|
|
228
|
+
}
|
|
229
|
+
console.log(`\nDDL written to .rad/ddl/`);
|
|
230
|
+
// Run Python seed via pyiceberg if available
|
|
231
|
+
const seedScript = buildPythonSeedScript(schemas, files);
|
|
232
|
+
const scriptPath = path_1.default.join(repoRoot, ".rad", "tmp", "seed.py");
|
|
233
|
+
fs_1.default.mkdirSync(path_1.default.dirname(scriptPath), { recursive: true });
|
|
234
|
+
fs_1.default.writeFileSync(scriptPath, seedScript);
|
|
235
|
+
const pythonBin = (_a = process.env.PYTHON) !== null && _a !== void 0 ? _a : "python";
|
|
236
|
+
try {
|
|
237
|
+
console.log(`\nSeeding Iceberg namespace "${env}" ...\n`);
|
|
238
|
+
(0, child_process_1.execSync)(`${pythonBin} "${scriptPath}"`, { stdio: "inherit" });
|
|
239
|
+
}
|
|
240
|
+
catch (_b) {
|
|
241
|
+
console.error("\nPython seed step failed. Ensure pyiceberg and polars are installed:\n" +
|
|
242
|
+
" pip install pyiceberg polars pyarrow\n\n" +
|
|
243
|
+
`Generated seed script: ${scriptPath}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
console.log("\nDone.");
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
main().catch((err) => {
|
|
250
|
+
console.error(err);
|
|
251
|
+
process.exit(1);
|
|
252
|
+
});
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { useFormik, FormikErrors } from "formik";
|
|
2
|
+
import {
|
|
3
|
+
FormFieldStructure,
|
|
4
|
+
FormProps,
|
|
5
|
+
FormStructure,
|
|
6
|
+
FormUpdate,
|
|
7
|
+
FormValuesType,
|
|
8
|
+
} from "./interfaces";
|
|
9
|
+
import { FlattenedRecord } from "../../types";
|
|
10
|
+
import { formikJSONSchemaValidation } from "./validate-schema";
|
|
11
|
+
import { getFieldStructure } from "./structure";
|
|
12
|
+
|
|
13
|
+
export function useForm<
|
|
14
|
+
TFormValues extends object,
|
|
15
|
+
TPostParams,
|
|
16
|
+
TPutParams,
|
|
17
|
+
TPatchParams,
|
|
18
|
+
TPostPayload,
|
|
19
|
+
TPutPayload,
|
|
20
|
+
TPatchPayload,
|
|
21
|
+
TPostResult,
|
|
22
|
+
TPutResult,
|
|
23
|
+
TPatchResult,
|
|
24
|
+
TFormUpdate extends FormUpdate,
|
|
25
|
+
TSourceId extends string | undefined,
|
|
26
|
+
>({
|
|
27
|
+
id,
|
|
28
|
+
sourceData,
|
|
29
|
+
formValues,
|
|
30
|
+
formatResponse,
|
|
31
|
+
transformRequest,
|
|
32
|
+
transformResponse,
|
|
33
|
+
mutations,
|
|
34
|
+
updateType,
|
|
35
|
+
onSuccess,
|
|
36
|
+
}: FormProps<
|
|
37
|
+
TFormValues,
|
|
38
|
+
TPostParams,
|
|
39
|
+
TPutParams,
|
|
40
|
+
TPatchParams,
|
|
41
|
+
TPostPayload,
|
|
42
|
+
TPutPayload,
|
|
43
|
+
TPatchPayload,
|
|
44
|
+
TPostResult,
|
|
45
|
+
TPutResult,
|
|
46
|
+
TPatchResult,
|
|
47
|
+
TFormUpdate,
|
|
48
|
+
TSourceId
|
|
49
|
+
>): FormStructure<
|
|
50
|
+
TFormValues,
|
|
51
|
+
FormValuesType<
|
|
52
|
+
TFormUpdate,
|
|
53
|
+
TSourceId,
|
|
54
|
+
TPutParams,
|
|
55
|
+
TPutPayload,
|
|
56
|
+
TPostParams,
|
|
57
|
+
TPostPayload,
|
|
58
|
+
TPatchParams,
|
|
59
|
+
TPatchPayload
|
|
60
|
+
>
|
|
61
|
+
> {
|
|
62
|
+
const updateMethod = id ? (updateType === "put" ? "put" : "patch") : "post";
|
|
63
|
+
|
|
64
|
+
const formatRequestValues = (values: TFormValues) => {
|
|
65
|
+
if (formatResponse) {
|
|
66
|
+
return (Object.keys(values) as Array<keyof TFormValues>).reduce(
|
|
67
|
+
(acc, key) => {
|
|
68
|
+
const formattedValue = formatResponse[key](values[key]);
|
|
69
|
+
return {
|
|
70
|
+
...acc,
|
|
71
|
+
...formattedValue,
|
|
72
|
+
};
|
|
73
|
+
},
|
|
74
|
+
{} as FormValuesType<
|
|
75
|
+
TFormUpdate,
|
|
76
|
+
TSourceId,
|
|
77
|
+
TPutParams,
|
|
78
|
+
TPutPayload,
|
|
79
|
+
TPostParams,
|
|
80
|
+
TPostPayload,
|
|
81
|
+
TPatchParams,
|
|
82
|
+
TPatchPayload
|
|
83
|
+
>,
|
|
84
|
+
);
|
|
85
|
+
} else if (transformRequest) {
|
|
86
|
+
return transformRequest(values);
|
|
87
|
+
} else {
|
|
88
|
+
return values;
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const isSourceDataLoading = sourceData
|
|
93
|
+
? Object.values(sourceData).some((source) =>
|
|
94
|
+
// @ts-ignore create a base type for list and fetch data extending react query. This will ensure that the isLoading property is available
|
|
95
|
+
source ? source.isLoading : false,
|
|
96
|
+
)
|
|
97
|
+
: false;
|
|
98
|
+
|
|
99
|
+
const formik = useFormik<TFormValues>({
|
|
100
|
+
initialValues: formValues,
|
|
101
|
+
validate: (values) => {
|
|
102
|
+
if (mutations) {
|
|
103
|
+
const payload = formatRequestValues(values);
|
|
104
|
+
return formikJSONSchemaValidation(
|
|
105
|
+
payload,
|
|
106
|
+
mutations[updateMethod]?.validation,
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
return {} as FormikErrors<TFormValues>;
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
onSubmit: (values, formikHelpers) => {
|
|
113
|
+
const mutation = mutations ? mutations[updateMethod].mutation : undefined;
|
|
114
|
+
const formattedResponse = formatRequestValues(values);
|
|
115
|
+
if (mutation) {
|
|
116
|
+
mutation.mutate(
|
|
117
|
+
{
|
|
118
|
+
//@ts-ignore
|
|
119
|
+
params: {
|
|
120
|
+
id,
|
|
121
|
+
...formattedResponse,
|
|
122
|
+
},
|
|
123
|
+
//@ts-ignore
|
|
124
|
+
payload: formattedResponse,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
onSuccess: (result) => {
|
|
128
|
+
if (onSuccess) {
|
|
129
|
+
// @ts-ignore
|
|
130
|
+
onSuccess(result);
|
|
131
|
+
}
|
|
132
|
+
formikHelpers.setSubmitting(false);
|
|
133
|
+
},
|
|
134
|
+
onError: () => {
|
|
135
|
+
formikHelpers.setSubmitting(false);
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
enableReinitialize: true,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const isSubmitDisabled =
|
|
145
|
+
formik.isValid === undefined ? false : !formik.isValid;
|
|
146
|
+
const isSubmitting = formik.isSubmitting;
|
|
147
|
+
const submitForm = () => {
|
|
148
|
+
formik.handleSubmit();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const getFormField = <
|
|
152
|
+
TFieldName extends Extract<keyof FlattenedRecord<TFormValues>, string>,
|
|
153
|
+
>(
|
|
154
|
+
fieldName: TFieldName,
|
|
155
|
+
): FormFieldStructure<FlattenedRecord<TFormValues>[TFieldName]> => {
|
|
156
|
+
const { value: value, checked: isCheckedValue } =
|
|
157
|
+
formik.getFieldProps(fieldName);
|
|
158
|
+
const { touched: isTouched, error: errorMessage } =
|
|
159
|
+
formik.getFieldMeta(fieldName);
|
|
160
|
+
const { setValue: setFormValue, setTouched } =
|
|
161
|
+
formik.getFieldHelpers(fieldName);
|
|
162
|
+
|
|
163
|
+
let { type, isRequired } = getFieldStructure(
|
|
164
|
+
mutations ? mutations[updateMethod]?.validation : undefined,
|
|
165
|
+
fieldName,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
const error = errorMessage ?? "";
|
|
169
|
+
const isError = errorMessage !== undefined;
|
|
170
|
+
const isDisabled = false;
|
|
171
|
+
const isChecked = isCheckedValue ?? false;
|
|
172
|
+
const setValue = (value: FlattenedRecord<TFormValues>[TFieldName]) => {
|
|
173
|
+
if (type === "number" && value) {
|
|
174
|
+
const numericValue = Number(value);
|
|
175
|
+
if (!isNaN(numericValue)) {
|
|
176
|
+
setFormValue(numericValue);
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
setFormValue(value);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
return {
|
|
183
|
+
type,
|
|
184
|
+
value,
|
|
185
|
+
error,
|
|
186
|
+
isChecked,
|
|
187
|
+
isError,
|
|
188
|
+
isDisabled,
|
|
189
|
+
isRequired,
|
|
190
|
+
setValue,
|
|
191
|
+
setTouched,
|
|
192
|
+
};
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
return {
|
|
196
|
+
formValues: formik.values,
|
|
197
|
+
// @ts-ignore
|
|
198
|
+
responseValues: formatRequestValues(formik.values),
|
|
199
|
+
errors: formik.errors,
|
|
200
|
+
getFormField,
|
|
201
|
+
onSubmit: submitForm,
|
|
202
|
+
resetForm: formik.resetForm,
|
|
203
|
+
isSubmitDisabled,
|
|
204
|
+
isSourceDataLoading,
|
|
205
|
+
isSubmitting,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { FlattenedRecord } from "../../types";
|
|
2
|
+
import { SchemaObject } from "ajv";
|
|
3
|
+
import { IUpdateQueryResult } from "../../hooks";
|
|
4
|
+
import { FormikValues } from "formik";
|
|
5
|
+
|
|
6
|
+
export interface FormFieldStructure<TValue> {
|
|
7
|
+
value: TValue;
|
|
8
|
+
error: string;
|
|
9
|
+
type: FormType;
|
|
10
|
+
isChecked: boolean;
|
|
11
|
+
isError: boolean;
|
|
12
|
+
isDisabled: boolean;
|
|
13
|
+
isRequired: boolean;
|
|
14
|
+
setValue: (value: TValue) => void;
|
|
15
|
+
setTouched: (isTouched: boolean) => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface FormMutation<
|
|
19
|
+
TPostParams,
|
|
20
|
+
TPutParams,
|
|
21
|
+
TPatchParams,
|
|
22
|
+
TPostPayload,
|
|
23
|
+
TPutPayload,
|
|
24
|
+
TPatchPayload,
|
|
25
|
+
TPostResult,
|
|
26
|
+
TPutResult,
|
|
27
|
+
TPatchResult,
|
|
28
|
+
> {
|
|
29
|
+
post: {
|
|
30
|
+
mutation?: IUpdateQueryResult<TPostParams, TPostPayload, TPostResult>;
|
|
31
|
+
validation?: SchemaObject | undefined;
|
|
32
|
+
};
|
|
33
|
+
put: {
|
|
34
|
+
mutation?: IUpdateQueryResult<TPutParams, TPutPayload, TPutResult>;
|
|
35
|
+
validation?: SchemaObject | undefined;
|
|
36
|
+
};
|
|
37
|
+
patch: {
|
|
38
|
+
mutation?: IUpdateQueryResult<TPatchParams, TPatchPayload, TPatchResult>;
|
|
39
|
+
validation?: SchemaObject | undefined;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type FormUpdate = "post_or_patch" | "put";
|
|
44
|
+
|
|
45
|
+
export interface FormProps<
|
|
46
|
+
TFormValues extends object,
|
|
47
|
+
TPostParams,
|
|
48
|
+
TPutParams,
|
|
49
|
+
TPatchParams,
|
|
50
|
+
TPostPayload,
|
|
51
|
+
TPutPayload,
|
|
52
|
+
TPatchPayload,
|
|
53
|
+
TPostResult,
|
|
54
|
+
TPutResult,
|
|
55
|
+
TPatchResult,
|
|
56
|
+
TFormUpdate extends FormUpdate,
|
|
57
|
+
TSourceId extends string | undefined,
|
|
58
|
+
> {
|
|
59
|
+
id?: TSourceId & string;
|
|
60
|
+
sourceData?: SourceData;
|
|
61
|
+
formValues: TFormValues & object;
|
|
62
|
+
formatResponse?: ForwardMapping<
|
|
63
|
+
TFormValues,
|
|
64
|
+
TFormUpdate extends "put"
|
|
65
|
+
? TPutParams & TPutResult
|
|
66
|
+
: TSourceId extends undefined
|
|
67
|
+
? TPostParams & TPostResult
|
|
68
|
+
: TPatchParams & TPatchResult
|
|
69
|
+
>;
|
|
70
|
+
transformRequest?: ForwardMap<
|
|
71
|
+
TFormValues,
|
|
72
|
+
TFormUpdate extends "put"
|
|
73
|
+
? TPutParams & TPutResult
|
|
74
|
+
: TSourceId extends undefined
|
|
75
|
+
? TPostParams & TPostResult
|
|
76
|
+
: TPatchParams & TPatchResult
|
|
77
|
+
>;
|
|
78
|
+
transformResponse?: ReverseMap<
|
|
79
|
+
TFormValues,
|
|
80
|
+
TFormUpdate extends "put"
|
|
81
|
+
? TPutParams & TPutResult
|
|
82
|
+
: TSourceId extends undefined
|
|
83
|
+
? TPostParams & TPostResult
|
|
84
|
+
: TPatchParams & TPatchResult
|
|
85
|
+
>;
|
|
86
|
+
updateType?: TFormUpdate;
|
|
87
|
+
mutations?: FormMutation<
|
|
88
|
+
TPostParams,
|
|
89
|
+
TPutParams,
|
|
90
|
+
TPatchParams,
|
|
91
|
+
TPostPayload,
|
|
92
|
+
TPutPayload,
|
|
93
|
+
TPatchPayload,
|
|
94
|
+
TPostResult,
|
|
95
|
+
TPutResult,
|
|
96
|
+
TPatchResult
|
|
97
|
+
>;
|
|
98
|
+
onSuccess?: (
|
|
99
|
+
values: TFormUpdate extends "put"
|
|
100
|
+
? TPutResult
|
|
101
|
+
: TSourceId extends undefined
|
|
102
|
+
? TPostResult
|
|
103
|
+
: TPatchResult,
|
|
104
|
+
) => void;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface FormStructure<TFormValues, TResponse> {
|
|
108
|
+
formValues: TFormValues | undefined;
|
|
109
|
+
responseValues: TResponse | undefined;
|
|
110
|
+
errors: any;
|
|
111
|
+
onSubmit: () => void;
|
|
112
|
+
resetForm: () => void;
|
|
113
|
+
isSubmitDisabled: boolean;
|
|
114
|
+
isSourceDataLoading: boolean;
|
|
115
|
+
isSubmitting: boolean;
|
|
116
|
+
getFormField: <TFieldName extends keyof FlattenedRecord<TFormValues>>(
|
|
117
|
+
fieldName: TFieldName,
|
|
118
|
+
) => FormFieldStructure<FlattenedRecord<TFormValues>[TFieldName]>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export type FormType = "number" | "text" | "array";
|
|
122
|
+
|
|
123
|
+
export interface SetupUpdateEndpointReturn<TParams, TPayload, TResult> {
|
|
124
|
+
mutation: IUpdateQueryResult<TParams, TPayload, TResult>;
|
|
125
|
+
validation: SchemaObject;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export type FormUpdateMethod = "post_or_patch" | "put";
|
|
129
|
+
|
|
130
|
+
export interface SourceData {
|
|
131
|
+
[key: string]: unknown;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export type ForwardMapping<TFormValues, TResponse> = {
|
|
135
|
+
[key in keyof TFormValues]: (value: TFormValues[key]) => {
|
|
136
|
+
[key in keyof TResponse]: TResponse[keyof TResponse];
|
|
137
|
+
};
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
export type ReverseMapping<TFormValues, TResponse> = {
|
|
141
|
+
[key in keyof TResponse]: (value: TResponse[key]) => {
|
|
142
|
+
[key in keyof TFormValues]: TFormValues[keyof TFormValues];
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export type ForwardMap<TFormValues, TResponse> = (value: TFormValues) => {
|
|
147
|
+
[key in keyof TResponse]: TResponse[keyof TResponse];
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
export type ReverseMap<TFormValues, TResponse> = (value: TResponse) => {
|
|
151
|
+
[key in keyof TFormValues]: TFormValues[keyof TFormValues];
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
type EnsureObject<T> = T extends object ? T : Record<string, never>;
|
|
155
|
+
|
|
156
|
+
export type FormValuesType<
|
|
157
|
+
TFormUpdate extends "put" | "post_or_patch",
|
|
158
|
+
TSourceId extends string | undefined,
|
|
159
|
+
TPutParams,
|
|
160
|
+
TPutPayload,
|
|
161
|
+
TPostParams,
|
|
162
|
+
TPostPayload,
|
|
163
|
+
TPatchParams,
|
|
164
|
+
TPatchPayload,
|
|
165
|
+
> = FormikValues &
|
|
166
|
+
EnsureObject<
|
|
167
|
+
TFormUpdate extends "put"
|
|
168
|
+
? TPutParams & TPutPayload
|
|
169
|
+
: TSourceId extends undefined
|
|
170
|
+
? TPostParams & TPostPayload
|
|
171
|
+
: TPatchParams & TPatchPayload
|
|
172
|
+
>;
|
|
173
|
+
|
|
174
|
+
export type FormParamsType<
|
|
175
|
+
TFormUpdate extends "put" | "post_or_patch",
|
|
176
|
+
TSourceId extends string | undefined,
|
|
177
|
+
TPutParams,
|
|
178
|
+
TPostParams,
|
|
179
|
+
TPatchParams,
|
|
180
|
+
> = FormikValues &
|
|
181
|
+
EnsureObject<
|
|
182
|
+
TFormUpdate extends "put"
|
|
183
|
+
? TPutParams
|
|
184
|
+
: TSourceId extends undefined
|
|
185
|
+
? TPostParams
|
|
186
|
+
: TPatchParams
|
|
187
|
+
>;
|
|
188
|
+
|
|
189
|
+
export type FormPayloadType<
|
|
190
|
+
TFormUpdate extends "put" | "post_or_patch",
|
|
191
|
+
TSourceId extends string | undefined,
|
|
192
|
+
TPutPayload,
|
|
193
|
+
TPostPayload,
|
|
194
|
+
TPatchPayload,
|
|
195
|
+
> = FormikValues &
|
|
196
|
+
EnsureObject<
|
|
197
|
+
TFormUpdate extends "put"
|
|
198
|
+
? TPutPayload
|
|
199
|
+
: TSourceId extends undefined
|
|
200
|
+
? TPostPayload
|
|
201
|
+
: TPatchPayload
|
|
202
|
+
>;
|
|
203
|
+
|
|
204
|
+
export type FormResultType<
|
|
205
|
+
TFormUpdate extends "put" | "post_or_patch",
|
|
206
|
+
TSourceId extends string | undefined,
|
|
207
|
+
TPutResult,
|
|
208
|
+
TPostResult,
|
|
209
|
+
TPatchResult,
|
|
210
|
+
> = FormikValues &
|
|
211
|
+
EnsureObject<
|
|
212
|
+
TFormUpdate extends "put"
|
|
213
|
+
? TPutResult
|
|
214
|
+
: TSourceId extends undefined
|
|
215
|
+
? TPostResult
|
|
216
|
+
: TPatchResult
|
|
217
|
+
>;
|