fastify-auto-sqlapi 0.0.4 → 0.1.1

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.
Files changed (127) hide show
  1. package/AGENTS.md +15 -2
  2. package/AGENTS_BACKEND.md +160 -32
  3. package/AGENTS_FRONTEND.md +220 -49
  4. package/README.md +147 -27
  5. package/dist/bin/generate-schema.js +12 -29
  6. package/dist/bin/generate-tables.js +10 -33
  7. package/dist/bin/utils.d.ts +29 -1
  8. package/dist/bin/utils.d.ts.map +1 -1
  9. package/dist/bin/utils.js +71 -2
  10. package/dist/index.d.ts +1 -1
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/lib/cli/load-dependency.d.ts +8 -0
  13. package/dist/lib/cli/load-dependency.d.ts.map +1 -0
  14. package/dist/lib/cli/load-dependency.js +21 -0
  15. package/dist/lib/cli/mysql-introspect.d.ts.map +1 -1
  16. package/dist/lib/cli/mysql-introspect.js +2 -11
  17. package/dist/lib/cli/pg-introspect.d.ts.map +1 -1
  18. package/dist/lib/cli/pg-introspect.js +2 -17
  19. package/dist/lib/cli/schema-codegen.d.ts +1 -1
  20. package/dist/lib/cli/schema-codegen.d.ts.map +1 -1
  21. package/dist/lib/cli/schema-codegen.js +11 -4
  22. package/dist/lib/cli/tables-codegen.d.ts.map +1 -1
  23. package/dist/lib/cli/tables-codegen.js +4 -3
  24. package/dist/lib/db.d.ts +1 -0
  25. package/dist/lib/db.d.ts.map +1 -1
  26. package/dist/lib/db.js +33 -49
  27. package/dist/lib/dialect.d.ts +17 -0
  28. package/dist/lib/dialect.d.ts.map +1 -1
  29. package/dist/lib/dialect.js +32 -0
  30. package/dist/lib/engine/bulk/bulk-upsert.d.ts.map +1 -1
  31. package/dist/lib/engine/bulk/bulk-upsert.js +26 -50
  32. package/dist/lib/engine/rest/delete.d.ts.map +1 -1
  33. package/dist/lib/engine/rest/delete.js +5 -10
  34. package/dist/lib/engine/rest/get.d.ts.map +1 -1
  35. package/dist/lib/engine/rest/get.js +4 -6
  36. package/dist/lib/engine/rest/insert.d.ts.map +1 -1
  37. package/dist/lib/engine/rest/insert.js +20 -39
  38. package/dist/lib/engine/rest/update.d.ts.map +1 -1
  39. package/dist/lib/engine/rest/update.js +35 -67
  40. package/dist/lib/engine/search/search.d.ts.map +1 -1
  41. package/dist/lib/engine/search/search.js +650 -130
  42. package/dist/lib/engine/write-helpers.d.ts +39 -2
  43. package/dist/lib/engine/write-helpers.d.ts.map +1 -1
  44. package/dist/lib/engine/write-helpers.js +58 -15
  45. package/dist/lib/errors.d.ts +11 -0
  46. package/dist/lib/errors.d.ts.map +1 -0
  47. package/dist/lib/errors.js +12 -0
  48. package/dist/lib/naming.d.ts +11 -2
  49. package/dist/lib/naming.d.ts.map +1 -1
  50. package/dist/lib/naming.js +33 -5
  51. package/dist/lib/schema/bulk-upsert.d.ts.map +1 -1
  52. package/dist/lib/schema/bulk-upsert.js +9 -25
  53. package/dist/lib/schema/helpers.d.ts +28 -1
  54. package/dist/lib/schema/helpers.d.ts.map +1 -1
  55. package/dist/lib/schema/helpers.js +38 -0
  56. package/dist/lib/schema/insert.d.ts.map +1 -1
  57. package/dist/lib/schema/insert.js +9 -19
  58. package/dist/lib/schema/search.d.ts.map +1 -1
  59. package/dist/lib/schema/search.js +147 -53
  60. package/dist/lib/schema/update.d.ts.map +1 -1
  61. package/dist/lib/schema/update.js +9 -25
  62. package/dist/lib/sql-api.d.ts +6 -6
  63. package/dist/lib/sql-api.d.ts.map +1 -1
  64. package/dist/lib/sql-api.js +8 -8
  65. package/dist/lib/table-helpers.d.ts +6 -1
  66. package/dist/lib/table-helpers.d.ts.map +1 -1
  67. package/dist/lib/table-helpers.js +51 -3
  68. package/dist/lib/tenant.d.ts +23 -0
  69. package/dist/lib/tenant.d.ts.map +1 -1
  70. package/dist/lib/tenant.js +57 -9
  71. package/dist/routes/auto/bulk-delete.routes.d.ts.map +1 -1
  72. package/dist/routes/auto/bulk-delete.routes.js +17 -23
  73. package/dist/routes/auto/bulk-upsert.routes.d.ts.map +1 -1
  74. package/dist/routes/auto/bulk-upsert.routes.js +16 -22
  75. package/dist/routes/auto/delete.routes.d.ts.map +1 -1
  76. package/dist/routes/auto/delete.routes.js +18 -24
  77. package/dist/routes/auto/get.routes.d.ts.map +1 -1
  78. package/dist/routes/auto/get.routes.js +16 -23
  79. package/dist/routes/auto/insert.routes.d.ts.map +1 -1
  80. package/dist/routes/auto/insert.routes.js +19 -26
  81. package/dist/routes/auto/route-helpers.d.ts +27 -2
  82. package/dist/routes/auto/route-helpers.d.ts.map +1 -1
  83. package/dist/routes/auto/route-helpers.js +39 -1
  84. package/dist/routes/auto/search.routes.d.ts.map +1 -1
  85. package/dist/routes/auto/search.routes.js +47 -47
  86. package/dist/routes/auto/update.routes.d.ts.map +1 -1
  87. package/dist/routes/auto/update.routes.js +20 -27
  88. package/dist/types/cli.d.ts +22 -0
  89. package/dist/types/cli.d.ts.map +1 -0
  90. package/dist/types/cli.js +1 -0
  91. package/dist/types/computed.d.ts +29 -0
  92. package/dist/types/computed.d.ts.map +1 -0
  93. package/dist/types/computed.js +1 -0
  94. package/dist/types/conditions.d.ts +16 -0
  95. package/dist/types/conditions.d.ts.map +1 -0
  96. package/dist/types/conditions.js +1 -0
  97. package/dist/types/db.d.ts +22 -0
  98. package/dist/types/db.d.ts.map +1 -0
  99. package/dist/types/db.js +1 -0
  100. package/dist/types/dml.d.ts +64 -0
  101. package/dist/types/dml.d.ts.map +1 -0
  102. package/dist/types/dml.js +1 -0
  103. package/dist/types/join.d.ts +44 -0
  104. package/dist/types/join.d.ts.map +1 -0
  105. package/dist/types/join.js +1 -0
  106. package/dist/types/plugin.d.ts +20 -0
  107. package/dist/types/plugin.d.ts.map +1 -0
  108. package/dist/types/plugin.js +1 -0
  109. package/dist/types/schema.d.ts +10 -0
  110. package/dist/types/schema.d.ts.map +1 -0
  111. package/dist/types/schema.js +1 -0
  112. package/dist/types/search.d.ts +45 -0
  113. package/dist/types/search.d.ts.map +1 -0
  114. package/dist/types/search.js +1 -0
  115. package/dist/types/table.d.ts +52 -0
  116. package/dist/types/table.d.ts.map +1 -0
  117. package/dist/types/table.js +7 -0
  118. package/dist/types/tenant.d.ts +19 -0
  119. package/dist/types/tenant.d.ts.map +1 -0
  120. package/dist/types/tenant.js +1 -0
  121. package/dist/types/validation.d.ts +21 -0
  122. package/dist/types/validation.d.ts.map +1 -0
  123. package/dist/types/validation.js +1 -0
  124. package/dist/types.d.ts +12 -258
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js +12 -7
  127. package/package.json +8 -5
@@ -1,58 +1,39 @@
1
- import { camelcaseObject, snakecaseRecord } from '../../naming.js';
2
- import { removeExcludedFields, processSecondaries } from '../write-helpers.js';
3
- import { injectTenantValue, validateTenantFK } from '../../tenant.js';
4
- import { runValidation } from '../validate.js';
1
+ import { camelcaseObject } from '../../naming.js';
2
+ import { processSecondaries, prepareInsertRecord } from '../write-helpers.js';
3
+ import { enforceTenantOnWrites } from '../../tenant.js';
5
4
  import { primaryAsString } from '../../../types.js';
6
5
  export async function insertEngine(params) {
7
6
  const { db, tableConf, dbTables, request, record, secondaries, tenant } = params;
7
+ const schema = tableConf.Schema;
8
8
  const pk = primaryAsString(tableConf.primary);
9
- const pkCol = tableConf.Schema.col(pk);
10
- // 1. Prepare main record
11
- let mainRecord = snakecaseRecord(record);
12
- mainRecord = removeExcludedFields(mainRecord, tableConf);
13
- // 1b. Tenant: inject or validate
14
- if (tenant) {
15
- if ('through' in tenant.scope) {
16
- // Indirect: validate FK belongs to tenant
17
- const scope = tenant.scope;
18
- const fkCol = scope.through.localField;
19
- const fkValue = mainRecord[fkCol];
20
- await validateTenantFK(db, scope, tenant.ids, [fkValue]);
21
- }
22
- else {
23
- injectTenantValue(mainRecord, tenant.scope, tenant.ids);
24
- }
25
- }
26
- // 2. Custom validation (receives original camelCase record)
27
- await runValidation(db, request, tableConf, record, secondaries);
28
- // 3. beforeInsert hook
29
- if (tableConf.beforeInsert) {
30
- await tableConf.beforeInsert(db, request, mainRecord);
31
- }
32
- // 4. Insert main → returns PK-only
9
+ const pkCol = schema.col(pk);
10
+ // 1. Validate, run beforeInsert hook, snakecase, drop excluded fields.
11
+ const { camel: inputRecord, snake: mainRecord } = await prepareInsertRecord({ db, tableConf, request }, record, secondaries);
12
+ // 2. Tenant: inject (direct) or validate FK (indirect) — enforced after user mutations
13
+ await enforceTenantOnWrites(db, tenant, [mainRecord]);
14
+ // 3. Insert main → returns PK-only
33
15
  let pkResult;
34
- const upsertKeys = tableConf.upsertMap?.get(tableConf.Schema);
16
+ const upsertKeys = tableConf.upsertMap?.get(schema);
35
17
  if (upsertKeys) {
36
- const conflictCols = upsertKeys.map((k) => tableConf.Schema.col(k));
37
- pkResult = await db.insertOrUpdate(tableConf.Schema.tableName, mainRecord, conflictCols, pkCol);
18
+ const conflictCols = upsertKeys.map((k) => schema.col(k));
19
+ pkResult = await db.insertOrUpdate(schema.tableName, mainRecord, conflictCols, pkCol);
38
20
  }
39
21
  else {
40
- pkResult = await db.insert(tableConf.Schema.tableName, mainRecord, pkCol);
22
+ pkResult = await db.insert(schema.tableName, mainRecord, pkCol);
41
23
  }
42
- const mainPkCamel = camelcaseObject(pkResult);
43
- // 5. Secondaries: need full record for FK auto-fill
24
+ const mainPkCamel = camelcaseObject(pkResult, schema);
25
+ // 4. Secondaries: need full record for FK auto-fill (camelCase)
44
26
  let secondaryResults;
45
27
  if (secondaries && Object.keys(secondaries).length > 0) {
46
- // Merge input record + PK for FK auto-fill
47
- const mainForFK = { ...camelcaseObject(mainRecord), ...mainPkCamel };
28
+ const mainForFK = { ...inputRecord, ...mainPkCamel };
48
29
  secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
49
30
  }
50
- // 6. afterInsert hook
31
+ // 5. afterInsert hook (camelCase — input merged with generated PK)
51
32
  if (tableConf.afterInsert) {
52
- const mainForHook = { ...camelcaseObject(mainRecord), ...mainPkCamel };
33
+ const mainForHook = { ...inputRecord, ...mainPkCamel };
53
34
  await tableConf.afterInsert(db, request, mainForHook, secondaryResults);
54
35
  }
55
- // 7. Return PK-only
36
+ // 6. Return PK-only
56
37
  const result = { main: mainPkCamel };
57
38
  if (secondaryResults && Object.keys(secondaryResults).length > 0) {
58
39
  result.secondaries = secondaryResults;
@@ -1 +1 @@
1
- {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/update.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAGb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CAgI9E"}
1
+ {"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/rest/update.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,YAAY,EACZ,YAAY,EAEb,MAAM,mBAAmB,CAAC;AAE3B,wBAAsB,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA+F9E"}
@@ -1,107 +1,75 @@
1
- import { camelcaseObject, snakecaseRecord } from '../../naming.js';
1
+ import { snakecaseRecord } from '../../naming.js';
2
2
  import { processSecondaries, processDeletions } from '../write-helpers.js';
3
- import { stripTenantColumn, buildTenantCondition, buildTenantJoin } from '../../tenant.js';
3
+ import { stripTenantColumn, assertTenantOwnership, buildTenantUpdateExtra, } from '../../tenant.js';
4
4
  import { runValidation } from '../validate.js';
5
- import { ConditionBuilder } from 'node-condition-builder';
5
+ import { httpError } from '../../errors.js';
6
6
  import { primaryAsString } from '../../../types.js';
7
7
  export async function updateEngine(params) {
8
8
  const { db, tableConf, dbTables, request, record, secondaries, deletions, tenant } = params;
9
- // 1. Prepare: extract PK, snakecase, build update fields
9
+ const schema = tableConf.Schema;
10
10
  const pk = primaryAsString(tableConf.primary);
11
- const pkCol = tableConf.Schema.col(pk);
12
- const snaked = snakecaseRecord(record);
13
- const pkValue = snaked[pkCol];
14
- if (pkValue == null) {
15
- const error = new Error(`Primary key "${pk}" is required`);
16
- error.statusCode = 400;
17
- throw error;
11
+ const pkCol = schema.col(pk);
12
+ const pkValue = record[pk];
13
+ if (pkValue == null)
14
+ throw httpError(400, `Primary key "${pk}" is required`);
15
+ // 1. Tenant ownership check (indirect) — do it early so user mutations don't run on records they can't access
16
+ await assertTenantOwnership(db, tenant, schema.tableName, pkCol, pkValue);
17
+ // 2. Mutable copy of input in camelCase
18
+ const inputRecord = { ...record };
19
+ // 3. Custom validation (camelCase)
20
+ await runValidation(db, request, tableConf, inputRecord, secondaries);
21
+ // 4. beforeUpdate hook (camelCase — user can mutate with schema field names)
22
+ if (tableConf.beforeUpdate) {
23
+ await tableConf.beforeUpdate(db, request, inputRecord);
18
24
  }
25
+ // 5. Convert to DB format (after all user mutations)
26
+ const snaked = snakecaseRecord(inputRecord, schema);
19
27
  const updateFields = { ...snaked };
20
28
  delete updateFields[pkCol];
21
- // 1b. Tenant: strip tenant column from update fields + validate ownership
29
+ // 6. Strip tenant column (user cannot change tenant of an existing record)
22
30
  if (tenant) {
23
31
  stripTenantColumn(updateFields, tenant.scope);
24
- if ('through' in tenant.scope) {
25
- // Indirect: verify the record belongs to tenant via subquery
26
- const scope = tenant.scope;
27
- const tableName = tableConf.Schema.tableName;
28
- const cb = new ConditionBuilder('AND');
29
- cb.isEqual(`${db.qi(tableName)}.${db.qi(pkCol)}`, pkValue);
30
- cb.append(buildTenantCondition(db, scope, tenant.ids));
31
- const checkWhere = cb.build(1, db.ph);
32
- const checkValues = cb.getValues();
33
- const joinSql = buildTenantJoin(db, scope, tableName);
34
- const checkSql = `SELECT 1 FROM ${db.qi(tableName)} ${joinSql} WHERE ${checkWhere} LIMIT 1`;
35
- const checkResult = await db.query(checkSql, checkValues);
36
- if (checkResult.rows.length === 0) {
37
- const error = new Error('Record not found');
38
- error.statusCode = 404;
39
- throw error;
40
- }
41
- }
42
32
  }
43
- // 2. Custom validation (receives original camelCase record)
44
- await runValidation(db, request, tableConf, record, secondaries);
45
- // 3. beforeUpdate hook
46
- if (tableConf.beforeUpdate) {
47
- await tableConf.beforeUpdate(db, request, updateFields);
48
- }
49
- // 4. Update main
33
+ // 7. Update main
50
34
  const hasFieldsToUpdate = Object.keys(updateFields).length > 0;
51
- let extraCondition;
52
- if (tenant && !('through' in tenant.scope)) {
53
- // Direct: add tenant to WHERE
54
- extraCondition = new ConditionBuilder('AND');
55
- if (tenant.ids.length === 1) {
56
- extraCondition.isEqual(tenant.scope.column, tenant.ids[0]);
57
- }
58
- else {
59
- extraCondition.isIn(tenant.scope.column, tenant.ids);
60
- }
61
- }
35
+ const extraCondition = buildTenantUpdateExtra(tenant);
62
36
  if (hasFieldsToUpdate) {
63
- const affectedRows = await db.update(tableConf.Schema.tableName, updateFields, { [pkCol]: pkValue }, extraCondition);
64
- if (affectedRows === 0) {
65
- const error = new Error(`Record not found`);
66
- error.statusCode = 404;
67
- throw error;
68
- }
37
+ const affectedRows = await db.update(schema.tableName, updateFields, { [pkCol]: pkValue }, extraCondition);
38
+ if (affectedRows === 0)
39
+ throw httpError(404, `Record not found`);
69
40
  }
70
41
  else {
71
42
  // No fields to update: verify the record exists (for secondaries/deletions)
72
- let whereSql = `${db.qi(tableConf.Schema.tableName)}.${db.qi(pkCol)} = ${db.ph(1)}`;
43
+ let whereSql = `${db.qi(schema.tableName)}.${db.qi(pkCol)} = ${db.ph(1)}`;
73
44
  const whereValues = [pkValue];
74
45
  if (extraCondition) {
75
46
  whereSql += ` AND ${extraCondition.build(2, db.ph)}`;
76
47
  whereValues.push(...extraCondition.getValues());
77
48
  }
78
49
  const rows = await db.select({
79
- tableName: tableConf.Schema.tableName,
50
+ tableName: schema.tableName,
80
51
  where: whereSql,
81
52
  values: whereValues,
82
53
  limit: '1',
83
54
  });
84
- if (rows.length === 0) {
85
- const error = new Error(`Record not found`);
86
- error.statusCode = 404;
87
- throw error;
88
- }
55
+ if (rows.length === 0)
56
+ throw httpError(404, `Record not found`);
89
57
  }
90
58
  // Build the main response (PK-only)
91
59
  const mainResult = { [pk]: pkValue };
92
- // 5. Secondaries (upsert/insert with FK auto-fill)
60
+ // 8. Secondaries (upsert/insert with FK auto-fill — camelCase)
93
61
  let secondaryResults;
94
62
  if (secondaries && Object.keys(secondaries).length > 0) {
95
- // Merge input fields + PK for FK auto-fill
96
- const mainForFK = { ...camelcaseObject(snaked) };
63
+ const mainForFK = { ...inputRecord };
97
64
  secondaryResults = await processSecondaries(db, tableConf, dbTables, mainForFK, secondaries);
98
65
  }
99
- // 6. Deletions
66
+ // 9. Deletions (FK auto-fill from main like secondaries)
100
67
  let deletionResults;
101
68
  if (deletions && Object.keys(deletions).length > 0) {
102
- deletionResults = await processDeletions(db, tableConf, deletions);
69
+ const mainForFK = { ...inputRecord };
70
+ deletionResults = await processDeletions(db, tableConf, mainForFK, deletions);
103
71
  }
104
- // 7. Return PK-only
72
+ // 10. Return PK-only
105
73
  const result = { main: mainResult };
106
74
  if (secondaryResults && Object.keys(secondaryResults).length > 0) {
107
75
  result.secondaries = secondaryResults;
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EAER,YAAY,EACZ,YAAY,EASb,MAAM,mBAAmB,CAAC;AA+V3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CAyDvB"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../../src/lib/engine/search/search.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,YAAY,EAab,MAAM,mBAAmB,CAAC;AAy/B3B,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,YAAY,GACnB,OAAO,CAAC,YAAY,CAAC,CAoIvB"}