@remix-run/data-table 0.0.0 → 0.2.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.
Files changed (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +549 -2
  3. package/dist/index.d.ts +15 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +7 -0
  6. package/dist/lib/adapter.d.ts +550 -0
  7. package/dist/lib/adapter.d.ts.map +1 -0
  8. package/dist/lib/adapter.js +1 -0
  9. package/dist/lib/column.d.ts +193 -0
  10. package/dist/lib/column.d.ts.map +1 -0
  11. package/dist/lib/column.js +302 -0
  12. package/dist/lib/database/execution-context.d.ts +10 -0
  13. package/dist/lib/database/execution-context.d.ts.map +1 -0
  14. package/dist/lib/database/execution-context.js +1 -0
  15. package/dist/lib/database/helpers.d.ts +26 -0
  16. package/dist/lib/database/helpers.d.ts.map +1 -0
  17. package/dist/lib/database/helpers.js +116 -0
  18. package/dist/lib/database/query-execution.d.ts +7 -0
  19. package/dist/lib/database/query-execution.d.ts.map +1 -0
  20. package/dist/lib/database/query-execution.js +401 -0
  21. package/dist/lib/database/relations.d.ts +4 -0
  22. package/dist/lib/database/relations.d.ts.map +1 -0
  23. package/dist/lib/database/relations.js +207 -0
  24. package/dist/lib/database/write-lifecycle.d.ts +13 -0
  25. package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
  26. package/dist/lib/database/write-lifecycle.js +279 -0
  27. package/dist/lib/database.d.ts +264 -0
  28. package/dist/lib/database.d.ts.map +1 -0
  29. package/dist/lib/database.js +319 -0
  30. package/dist/lib/errors.d.ts +59 -0
  31. package/dist/lib/errors.d.ts.map +1 -0
  32. package/dist/lib/errors.js +76 -0
  33. package/dist/lib/inflection.d.ts +3 -0
  34. package/dist/lib/inflection.d.ts.map +1 -0
  35. package/dist/lib/inflection.js +56 -0
  36. package/dist/lib/migrations/filename.d.ts +12 -0
  37. package/dist/lib/migrations/filename.d.ts.map +1 -0
  38. package/dist/lib/migrations/filename.js +20 -0
  39. package/dist/lib/migrations/helpers.d.ts +11 -0
  40. package/dist/lib/migrations/helpers.d.ts.map +1 -0
  41. package/dist/lib/migrations/helpers.js +77 -0
  42. package/dist/lib/migrations/journal-store.d.ts +15 -0
  43. package/dist/lib/migrations/journal-store.d.ts.map +1 -0
  44. package/dist/lib/migrations/journal-store.js +83 -0
  45. package/dist/lib/migrations/registry.d.ts +27 -0
  46. package/dist/lib/migrations/registry.d.ts.map +1 -0
  47. package/dist/lib/migrations/registry.js +51 -0
  48. package/dist/lib/migrations/runner.d.ts +20 -0
  49. package/dist/lib/migrations/runner.d.ts.map +1 -0
  50. package/dist/lib/migrations/runner.js +273 -0
  51. package/dist/lib/migrations/schema-api.d.ts +7 -0
  52. package/dist/lib/migrations/schema-api.d.ts.map +1 -0
  53. package/dist/lib/migrations/schema-api.js +326 -0
  54. package/dist/lib/migrations-node.d.ts +17 -0
  55. package/dist/lib/migrations-node.d.ts.map +1 -0
  56. package/dist/lib/migrations-node.js +65 -0
  57. package/dist/lib/migrations.d.ts +292 -0
  58. package/dist/lib/migrations.d.ts.map +1 -0
  59. package/dist/lib/migrations.js +38 -0
  60. package/dist/lib/operators.d.ts +154 -0
  61. package/dist/lib/operators.d.ts.map +1 -0
  62. package/dist/lib/operators.js +218 -0
  63. package/dist/lib/query.d.ts +159 -0
  64. package/dist/lib/query.d.ts.map +1 -0
  65. package/dist/lib/query.js +401 -0
  66. package/dist/lib/references.d.ts +41 -0
  67. package/dist/lib/references.d.ts.map +1 -0
  68. package/dist/lib/references.js +33 -0
  69. package/dist/lib/sql-helpers.d.ts +50 -0
  70. package/dist/lib/sql-helpers.d.ts.map +1 -0
  71. package/dist/lib/sql-helpers.js +111 -0
  72. package/dist/lib/sql.d.ts +45 -0
  73. package/dist/lib/sql.d.ts.map +1 -0
  74. package/dist/lib/sql.js +65 -0
  75. package/dist/lib/table.d.ts +569 -0
  76. package/dist/lib/table.d.ts.map +1 -0
  77. package/dist/lib/table.js +519 -0
  78. package/dist/lib/types.d.ts +4 -0
  79. package/dist/lib/types.d.ts.map +1 -0
  80. package/dist/lib/types.js +1 -0
  81. package/dist/migrations/node.d.ts +2 -0
  82. package/dist/migrations/node.d.ts.map +1 -0
  83. package/dist/migrations/node.js +1 -0
  84. package/dist/migrations.d.ts +8 -0
  85. package/dist/migrations.d.ts.map +1 -0
  86. package/dist/migrations.js +5 -0
  87. package/dist/operators.d.ts +3 -0
  88. package/dist/operators.d.ts.map +1 -0
  89. package/dist/operators.js +1 -0
  90. package/dist/sql-helpers.d.ts +3 -0
  91. package/dist/sql-helpers.d.ts.map +1 -0
  92. package/dist/sql-helpers.js +1 -0
  93. package/package.json +56 -7
  94. package/src/index.ts +198 -0
  95. package/src/lib/adapter.ts +653 -0
  96. package/src/lib/column.ts +384 -0
  97. package/src/lib/database/execution-context.ts +15 -0
  98. package/src/lib/database/helpers.ts +216 -0
  99. package/src/lib/database/query-execution.ts +638 -0
  100. package/src/lib/database/relations.ts +332 -0
  101. package/src/lib/database/write-lifecycle.ts +487 -0
  102. package/src/lib/database.ts +856 -0
  103. package/src/lib/errors.ts +119 -0
  104. package/src/lib/inflection.ts +69 -0
  105. package/src/lib/migrations/filename.ts +25 -0
  106. package/src/lib/migrations/helpers.ts +108 -0
  107. package/src/lib/migrations/journal-store.ts +122 -0
  108. package/src/lib/migrations/registry.ts +62 -0
  109. package/src/lib/migrations/runner.ts +374 -0
  110. package/src/lib/migrations/schema-api.ts +417 -0
  111. package/src/lib/migrations-node.ts +71 -0
  112. package/src/lib/migrations.ts +328 -0
  113. package/src/lib/operators.ts +436 -0
  114. package/src/lib/query.ts +958 -0
  115. package/src/lib/references.ts +78 -0
  116. package/src/lib/sql-helpers.ts +146 -0
  117. package/src/lib/sql.ts +84 -0
  118. package/src/lib/table.ts +1309 -0
  119. package/src/lib/types.ts +3 -0
  120. package/src/migrations/node.ts +1 -0
  121. package/src/migrations.ts +26 -0
  122. package/src/operators.ts +18 -0
  123. package/src/sql-helpers.ts +9 -0
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Base error for all `data-table` failures.
3
+ */
4
+ export class DataTableError extends Error {
5
+ /**
6
+ * Stable error code identifying the failure category.
7
+ */
8
+ code: string
9
+
10
+ /**
11
+ * Optional structured metadata attached to the failure.
12
+ */
13
+ metadata?: Record<string, unknown>
14
+
15
+ constructor(
16
+ message: string,
17
+ options?: {
18
+ code?: string
19
+ cause?: unknown
20
+ metadata?: Record<string, unknown>
21
+ },
22
+ ) {
23
+ super(message, { cause: options?.cause })
24
+ this.name = 'DataTableError'
25
+ this.code = options?.code ?? 'DATA_TABLE_ERROR'
26
+ this.metadata = options?.metadata
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Thrown when input data fails schema validation.
32
+ */
33
+ export class DataTableValidationError extends DataTableError {
34
+ /**
35
+ * Validation issues reported by the schema validator.
36
+ */
37
+ issues: ReadonlyArray<unknown>
38
+
39
+ constructor(
40
+ message: string,
41
+ issues: ReadonlyArray<unknown>,
42
+ options?: {
43
+ cause?: unknown
44
+ metadata?: Record<string, unknown>
45
+ },
46
+ ) {
47
+ super(message, {
48
+ code: 'DATA_TABLE_VALIDATION_ERROR',
49
+ cause: options?.cause,
50
+ metadata: options?.metadata,
51
+ })
52
+
53
+ this.name = 'DataTableValidationError'
54
+ this.issues = issues
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Thrown when a query is invalid for the current builder state.
60
+ */
61
+ export class DataTableQueryError extends DataTableError {
62
+ constructor(
63
+ message: string,
64
+ options?: {
65
+ cause?: unknown
66
+ metadata?: Record<string, unknown>
67
+ },
68
+ ) {
69
+ super(message, {
70
+ code: 'DATA_TABLE_QUERY_ERROR',
71
+ cause: options?.cause,
72
+ metadata: options?.metadata,
73
+ })
74
+
75
+ this.name = 'DataTableQueryError'
76
+ }
77
+ }
78
+
79
+ /**
80
+ * Thrown when adapter execution fails.
81
+ */
82
+ export class DataTableAdapterError extends DataTableError {
83
+ constructor(
84
+ message: string,
85
+ options?: {
86
+ cause?: unknown
87
+ metadata?: Record<string, unknown>
88
+ },
89
+ ) {
90
+ super(message, {
91
+ code: 'DATA_TABLE_ADAPTER_ERROR',
92
+ cause: options?.cause,
93
+ metadata: options?.metadata,
94
+ })
95
+
96
+ this.name = 'DataTableAdapterError'
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Thrown when a database constraint is violated.
102
+ */
103
+ export class DataTableConstraintError extends DataTableError {
104
+ constructor(
105
+ message: string,
106
+ options?: {
107
+ cause?: unknown
108
+ metadata?: Record<string, unknown>
109
+ },
110
+ ) {
111
+ super(message, {
112
+ code: 'DATA_TABLE_CONSTRAINT_ERROR',
113
+ cause: options?.cause,
114
+ metadata: options?.metadata,
115
+ })
116
+
117
+ this.name = 'DataTableConstraintError'
118
+ }
119
+ }
@@ -0,0 +1,69 @@
1
+ const IRREGULAR_SINGULAR_FORMS: Record<string, string> = {
2
+ people: 'person',
3
+ men: 'man',
4
+ women: 'woman',
5
+ children: 'child',
6
+ teeth: 'tooth',
7
+ feet: 'foot',
8
+ geese: 'goose',
9
+ mice: 'mouse',
10
+ data: 'datum',
11
+ media: 'medium',
12
+ indices: 'index',
13
+ vertices: 'vertex',
14
+ analyses: 'analysis',
15
+ statuses: 'status',
16
+ categories: 'category',
17
+ companies: 'company',
18
+ addresses: 'address',
19
+ }
20
+
21
+ export function singularize(word: string): string {
22
+ let lower = word.toLowerCase()
23
+
24
+ if (IRREGULAR_SINGULAR_FORMS[lower]) {
25
+ return preserveCase(word, IRREGULAR_SINGULAR_FORMS[lower])
26
+ }
27
+
28
+ if (lower.endsWith('ies') && lower.length > 3) {
29
+ return preserveCase(word, word.slice(0, -3) + 'y')
30
+ }
31
+
32
+ if (lower.endsWith('sses') || lower.endsWith('shes') || lower.endsWith('ches')) {
33
+ return preserveCase(word, word.slice(0, -2))
34
+ }
35
+
36
+ if (lower.endsWith('xes') || lower.endsWith('zes')) {
37
+ return preserveCase(word, word.slice(0, -2))
38
+ }
39
+
40
+ if (lower.endsWith('s') && !lower.endsWith('ss')) {
41
+ return preserveCase(word, word.slice(0, -1))
42
+ }
43
+
44
+ return word
45
+ }
46
+
47
+ export function inferForeignKey(tableName: string): string {
48
+ let segments = tableName.split('_')
49
+ let tail = segments.pop() ?? tableName
50
+ let singularTail = singularize(tail)
51
+
52
+ if (segments.length === 0) {
53
+ return singularTail + '_id'
54
+ }
55
+
56
+ return [...segments, singularTail + '_id'].join('_')
57
+ }
58
+
59
+ function preserveCase(source: string, replacement: string): string {
60
+ if (source.length === 0) {
61
+ return replacement
62
+ }
63
+
64
+ if (source[0] === source[0].toUpperCase()) {
65
+ return replacement[0].toUpperCase() + replacement.slice(1)
66
+ }
67
+
68
+ return replacement
69
+ }
@@ -0,0 +1,25 @@
1
+ const migrationFilenamePattern = /^(\d{14})_(.+)\.(?:m?ts|m?js|cts|cjs)$/
2
+
3
+ /**
4
+ * Parses a migration filename into `{ id, name }`.
5
+ *
6
+ * Expected format: `YYYYMMDDHHmmss_name.(ts|js|mts|mjs|cts|cjs)`.
7
+ * @param filename Migration file basename.
8
+ * @returns Parsed migration id and name.
9
+ */
10
+ export function parseMigrationFilename(filename: string): { id: string; name: string } {
11
+ let match = filename.match(migrationFilenamePattern)
12
+
13
+ if (!match) {
14
+ throw new Error(
15
+ 'Invalid migration filename "' +
16
+ filename +
17
+ '". Expected format YYYYMMDDHHmmss_name.ts (or .js/.mts/.mjs/.cts/.cjs)',
18
+ )
19
+ }
20
+
21
+ return {
22
+ id: match[1],
23
+ name: match[2],
24
+ }
25
+ }
@@ -0,0 +1,108 @@
1
+ import type { TableRef } from '../adapter.ts'
2
+ import type { IndexColumns, KeyColumns } from '../migrations.ts'
3
+
4
+ export function toTableRef(name: string): TableRef {
5
+ let segments = name.split('.')
6
+
7
+ if (segments.length === 1) {
8
+ return { name }
9
+ }
10
+
11
+ return {
12
+ schema: segments[0],
13
+ name: segments.slice(1).join('.'),
14
+ }
15
+ }
16
+
17
+ export function normalizeIndexColumns(columns: IndexColumns): string[] {
18
+ return normalizeKeyColumns(columns)
19
+ }
20
+
21
+ export function normalizeKeyColumns(columns: KeyColumns): string[] {
22
+ if (Array.isArray(columns)) {
23
+ return [...columns]
24
+ }
25
+
26
+ return [columns]
27
+ }
28
+
29
+ function normalizeNamePart(value: string): string {
30
+ let normalized = value
31
+ .toLowerCase()
32
+ .replace(/[^a-z0-9]+/g, '_')
33
+ .replace(/^_+|_+$/g, '')
34
+
35
+ if (normalized.length === 0) {
36
+ return 'item'
37
+ }
38
+
39
+ return normalized
40
+ }
41
+
42
+ function tableNamePart(table: TableRef): string {
43
+ if (table.schema) {
44
+ return normalizeNamePart(table.schema + '_' + table.name)
45
+ }
46
+
47
+ return normalizeNamePart(table.name)
48
+ }
49
+
50
+ function hashString(value: string): number {
51
+ let hash = 5381
52
+
53
+ for (let index = 0; index < value.length; index++) {
54
+ hash = ((hash << 5) + hash) ^ value.charCodeAt(index)
55
+ }
56
+
57
+ return hash >>> 0
58
+ }
59
+
60
+ function withNameLimit(name: string): string {
61
+ let limit = 63
62
+
63
+ if (name.length <= limit) {
64
+ return name
65
+ }
66
+
67
+ let suffix = hashString(name).toString(36).padStart(8, '0').slice(0, 8)
68
+ return name.slice(0, limit - 9) + '_' + suffix
69
+ }
70
+
71
+ function columnsNamePart(columns: string[]): string {
72
+ return columns.map((column) => normalizeNamePart(column)).join('_')
73
+ }
74
+
75
+ export function createPrimaryKeyName(table: TableRef): string {
76
+ return withNameLimit(tableNamePart(table) + '_pk')
77
+ }
78
+
79
+ export function createUniqueName(table: TableRef, columns: string[]): string {
80
+ return withNameLimit(tableNamePart(table) + '_' + columnsNamePart(columns) + '_uq')
81
+ }
82
+
83
+ export function createForeignKeyName(
84
+ table: TableRef,
85
+ columns: string[],
86
+ references: TableRef,
87
+ referenceColumns: string[],
88
+ ): string {
89
+ let base =
90
+ tableNamePart(table) +
91
+ '_' +
92
+ columnsNamePart(columns) +
93
+ '_' +
94
+ tableNamePart(references) +
95
+ '_' +
96
+ columnsNamePart(referenceColumns) +
97
+ '_fk'
98
+ return withNameLimit(base)
99
+ }
100
+
101
+ export function createCheckName(table: TableRef, expression: string): string {
102
+ let suffix = hashString(expression).toString(36)
103
+ return withNameLimit(tableNamePart(table) + '_chk_' + suffix)
104
+ }
105
+
106
+ export function createIndexName(table: TableRef, columns: string[]): string {
107
+ return withNameLimit(tableNamePart(table) + '_' + columnsNamePart(columns) + '_idx')
108
+ }
@@ -0,0 +1,122 @@
1
+ import type { DatabaseAdapter, TransactionToken } from '../adapter.ts'
2
+ import { rawSql } from '../sql.ts'
3
+ import type { MigrationDescriptor, MigrationJournalRow } from '../migrations.ts'
4
+
5
+ export function normalizeChecksum(migration: MigrationDescriptor): string {
6
+ if (migration.checksum) {
7
+ return migration.checksum
8
+ }
9
+
10
+ return migration.id + ':' + migration.name
11
+ }
12
+
13
+ export async function ensureMigrationJournal(
14
+ adapter: DatabaseAdapter,
15
+ tableName: string,
16
+ ): Promise<void> {
17
+ await adapter.migrate({
18
+ operation: {
19
+ kind: 'createTable',
20
+ table: { name: tableName },
21
+ ifNotExists: true,
22
+ columns: {
23
+ id: { type: 'varchar', length: 64, nullable: false, primaryKey: true },
24
+ name: { type: 'varchar', length: 255, nullable: false },
25
+ checksum: { type: 'varchar', length: 128, nullable: false },
26
+ batch: { type: 'integer', nullable: false },
27
+ applied_at: { type: 'timestamp', nullable: false, default: { kind: 'now' } },
28
+ },
29
+ },
30
+ })
31
+ }
32
+
33
+ export async function hasMigrationJournal(
34
+ adapter: DatabaseAdapter,
35
+ tableName: string,
36
+ ): Promise<boolean> {
37
+ try {
38
+ await adapter.execute({
39
+ operation: {
40
+ kind: 'raw',
41
+ sql: rawSql('select 1 from ' + tableName + ' limit 1'),
42
+ },
43
+ })
44
+
45
+ return true
46
+ } catch {
47
+ return false
48
+ }
49
+ }
50
+
51
+ export async function loadJournalRows(
52
+ adapter: DatabaseAdapter,
53
+ tableName: string,
54
+ ): Promise<MigrationJournalRow[]> {
55
+ let result = await adapter.execute({
56
+ operation: {
57
+ kind: 'raw',
58
+ sql: rawSql(
59
+ 'select id, name, checksum, batch, applied_at from ' + tableName + ' order by id asc',
60
+ ),
61
+ },
62
+ })
63
+
64
+ let rows = result.rows ?? []
65
+
66
+ return rows.map((row) => ({
67
+ id: String(row.id),
68
+ name: String(row.name),
69
+ checksum: String(row.checksum),
70
+ batch: Number(row.batch),
71
+ appliedAt: new Date(String(row.applied_at)),
72
+ }))
73
+ }
74
+
75
+ export async function insertJournalRow(
76
+ adapter: DatabaseAdapter,
77
+ tableName: string,
78
+ row: {
79
+ id: string
80
+ name: string
81
+ checksum: string
82
+ batch: number
83
+ },
84
+ transaction?: TransactionToken,
85
+ ): Promise<void> {
86
+ await adapter.execute({
87
+ operation: {
88
+ kind: 'raw',
89
+ sql: rawSql('insert into ' + tableName + ' (id, name, checksum, batch) values (?, ?, ?, ?)', [
90
+ row.id,
91
+ row.name,
92
+ row.checksum,
93
+ row.batch,
94
+ ]),
95
+ },
96
+ transaction,
97
+ })
98
+ }
99
+
100
+ export async function deleteJournalRow(
101
+ adapter: DatabaseAdapter,
102
+ tableName: string,
103
+ id: string,
104
+ transaction?: TransactionToken,
105
+ ): Promise<void> {
106
+ await adapter.execute({
107
+ operation: {
108
+ kind: 'raw',
109
+ sql: rawSql('delete from ' + tableName + ' where id = ?', [id]),
110
+ },
111
+ transaction,
112
+ })
113
+ }
114
+
115
+ export function getBatch(rows: MigrationJournalRow[]): number {
116
+ if (rows.length === 0) {
117
+ return 1
118
+ }
119
+
120
+ let max = Math.max(...rows.map((row) => row.batch))
121
+ return max + 1
122
+ }
@@ -0,0 +1,62 @@
1
+ import type { MigrationDescriptor, MigrationRegistry } from '../migrations.ts'
2
+
3
+ /**
4
+ * Returns a new array of migrations ordered by migration id.
5
+ * @param migrations Migration descriptors to sort.
6
+ * @returns A newly sorted migration descriptor array.
7
+ */
8
+ export function sortMigrations(migrations: MigrationDescriptor[]): MigrationDescriptor[] {
9
+ return [...migrations].sort((left, right) => left.id.localeCompare(right.id))
10
+ }
11
+
12
+ /**
13
+ * Resolves a migration source into a sorted migration list.
14
+ * @param input Migration list or registry.
15
+ * @returns A sorted migration descriptor list.
16
+ */
17
+ export function resolveMigrations(
18
+ input: MigrationDescriptor[] | MigrationRegistry,
19
+ ): MigrationDescriptor[] {
20
+ if (Array.isArray(input)) {
21
+ return sortMigrations(input)
22
+ }
23
+
24
+ return input.list()
25
+ }
26
+
27
+ /**
28
+ * Creates an in-memory migration registry.
29
+ * @param initial Optional initial migration list.
30
+ * @returns A migration registry with duplicate-id protection.
31
+ * @example
32
+ * ```ts
33
+ * import { createMigrationRegistry } from 'remix/data-table/migrations'
34
+ *
35
+ * let registry = createMigrationRegistry()
36
+ * registry.register({ id, name, migration })
37
+ * ```
38
+ */
39
+ export function createMigrationRegistry(initial: MigrationDescriptor[] = []): MigrationRegistry {
40
+ let migrations = new Map<string, MigrationDescriptor>()
41
+
42
+ for (let migration of initial) {
43
+ if (migrations.has(migration.id)) {
44
+ throw new Error('Duplicate migration id: ' + migration.id)
45
+ }
46
+
47
+ migrations.set(migration.id, migration)
48
+ }
49
+
50
+ return {
51
+ register(migration: MigrationDescriptor) {
52
+ if (migrations.has(migration.id)) {
53
+ throw new Error('Duplicate migration id: ' + migration.id)
54
+ }
55
+
56
+ migrations.set(migration.id, migration)
57
+ },
58
+ list() {
59
+ return sortMigrations(Array.from(migrations.values()))
60
+ },
61
+ }
62
+ }