@syncular/server 0.15.9 → 0.15.10

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/dist/seed.js CHANGED
@@ -12,8 +12,26 @@
12
12
  import { decodeMessage, encodeMessage, encodeRow, PROTOCOL_WIRE_VERSION, } from '@syncular/core';
13
13
  import { SyncError } from './errors.js';
14
14
  import { handleSyncRequest } from './handler.js';
15
- function camelToSnake(name) {
16
- return name.replace(/[A-Z]/g, (ch) => `_${ch.toLowerCase()}`);
15
+ const MAPPABLE_RE = /^_*[A-Za-z][A-Za-z0-9_]*$/;
16
+ /** Pinned §12 schema alias used by generated row types and every client host. */
17
+ function snakeToCamel(name) {
18
+ if (!MAPPABLE_RE.test(name))
19
+ return name;
20
+ const lead = /^_*/.exec(name)?.[0] ?? '';
21
+ const bare = name.slice(lead.length);
22
+ const trail = /_*$/.exec(bare)?.[0] ?? '';
23
+ const middle = bare.slice(0, bare.length - trail.length);
24
+ const segments = middle.split('_').filter((segment) => segment.length > 0);
25
+ if (segments.length === 0)
26
+ return name;
27
+ const first = segments[0];
28
+ return (lead +
29
+ first +
30
+ segments
31
+ .slice(1)
32
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
33
+ .join('') +
34
+ trail);
17
35
  }
18
36
  /**
19
37
  * Seed `mutations` into a partition through the real push path. Throws a
@@ -35,12 +53,11 @@ export async function seedMutations(config, target, mutations) {
35
53
  // the two casings the client tier accepts).
36
54
  const byColumn = new Map();
37
55
  for (const [key, value] of Object.entries(mutation.values)) {
38
- const name = table.columns.some((c) => c.name === key)
39
- ? key
40
- : camelToSnake(key);
41
- if (!table.columns.some((c) => c.name === name)) {
56
+ const column = table.columns.find((candidate) => candidate.name === key || snakeToCamel(candidate.name) === key);
57
+ if (column === undefined) {
42
58
  throw new SyncError('sync.invalid_request', `seedMutations: table ${table.name}: unknown column ${JSON.stringify(key)}`);
43
59
  }
60
+ const name = column.name;
44
61
  if (byColumn.has(name)) {
45
62
  throw new SyncError('sync.invalid_request', `seedMutations: table ${table.name}: column ${JSON.stringify(name)} appears twice (snake_case and camelCase) — pass it once`);
46
63
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@syncular/server",
3
- "version": "0.15.9",
3
+ "version": "0.15.10",
4
4
  "description": "Syncular server: handleSyncRequest + storage/auth interfaces for the sync protocol",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Benjamin Kniffler",
@@ -53,7 +53,7 @@
53
53
  "!dist/**/*.test.d.ts"
54
54
  ],
55
55
  "dependencies": {
56
- "@syncular/core": "0.15.9"
56
+ "@syncular/core": "0.15.10"
57
57
  },
58
58
  "devDependencies": {
59
59
  "@electric-sql/pglite": "^0.5.4"
package/src/seed.ts CHANGED
@@ -54,8 +54,27 @@ export interface SeedTarget {
54
54
  readonly commitId?: string;
55
55
  }
56
56
 
57
- function camelToSnake(name: string): string {
58
- return name.replace(/[A-Z]/g, (ch) => `_${ch.toLowerCase()}`);
57
+ const MAPPABLE_RE = /^_*[A-Za-z][A-Za-z0-9_]*$/;
58
+
59
+ /** Pinned §12 schema alias used by generated row types and every client host. */
60
+ function snakeToCamel(name: string): string {
61
+ if (!MAPPABLE_RE.test(name)) return name;
62
+ const lead = /^_*/.exec(name)?.[0] ?? '';
63
+ const bare = name.slice(lead.length);
64
+ const trail = /_*$/.exec(bare)?.[0] ?? '';
65
+ const middle = bare.slice(0, bare.length - trail.length);
66
+ const segments = middle.split('_').filter((segment) => segment.length > 0);
67
+ if (segments.length === 0) return name;
68
+ const first = segments[0] as string;
69
+ return (
70
+ lead +
71
+ first +
72
+ segments
73
+ .slice(1)
74
+ .map((segment) => segment.charAt(0).toUpperCase() + segment.slice(1))
75
+ .join('') +
76
+ trail
77
+ );
59
78
  }
60
79
 
61
80
  /**
@@ -85,15 +104,17 @@ export async function seedMutations(
85
104
  // the two casings the client tier accepts).
86
105
  const byColumn = new Map<string, unknown>();
87
106
  for (const [key, value] of Object.entries(mutation.values)) {
88
- const name = table.columns.some((c) => c.name === key)
89
- ? key
90
- : camelToSnake(key);
91
- if (!table.columns.some((c) => c.name === name)) {
107
+ const column = table.columns.find(
108
+ (candidate) =>
109
+ candidate.name === key || snakeToCamel(candidate.name) === key,
110
+ );
111
+ if (column === undefined) {
92
112
  throw new SyncError(
93
113
  'sync.invalid_request',
94
114
  `seedMutations: table ${table.name}: unknown column ${JSON.stringify(key)}`,
95
115
  );
96
116
  }
117
+ const name = column.name;
97
118
  if (byColumn.has(name)) {
98
119
  throw new SyncError(
99
120
  'sync.invalid_request',