w-orm-reladb 1.1.12 → 1.1.14

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.
@@ -29,7 +29,7 @@
29
29
  <nav >
30
30
 
31
31
 
32
- <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmReladb.html">WOrmReladb</a><ul class='methods'><li data-type='method'><a href="WOrmReladb.html#.closeSequelize">closeSequelize</a></li><li data-type='method'><a href="WOrmReladb.html#.createStorage">createStorage</a></li><li data-type='method'><a href="WOrmReladb.html#.del">del</a></li><li data-type='method'><a href="WOrmReladb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmReladb.html#.genModelsByDB">genModelsByDB</a></li><li data-type='method'><a href="WOrmReladb.html#.genModelsByTabs">genModelsByTabs</a></li><li data-type='method'><a href="WOrmReladb.html#.genTransaction">genTransaction</a></li><li data-type='method'><a href="WOrmReladb.html#.initSequelize">initSequelize</a></li><li data-type='method'><a href="WOrmReladb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmReladb.html#.insertBulk">insertBulk</a></li><li data-type='method'><a href="WOrmReladb.html#.save">save</a></li><li data-type='method'><a href="WOrmReladb.html#.select">select</a></li><li data-type='method'><a href="WOrmReladb.html#.selectByPk">selectByPk</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#cvFind">cvFind</a></li><li><a href="global.html#genDateChecker">genDateChecker</a></li><li><a href="global.html#getColKind">getColKind</a></li><li><a href="global.html#getColScale">getColScale</a></li><li><a href="global.html#importModels">importModels</a></li><li><a href="global.html#inDateRange">inDateRange</a></li><li><a href="global.html#isKindOk">isKindOk</a></li><li><a href="global.html#normPk">normPk</a></li></ul>
32
+ <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="WOrmReladb.html">WOrmReladb</a><ul class='methods'><li data-type='method'><a href="WOrmReladb.html#.closeSequelize">closeSequelize</a></li><li data-type='method'><a href="WOrmReladb.html#.createStorage">createStorage</a></li><li data-type='method'><a href="WOrmReladb.html#.del">del</a></li><li data-type='method'><a href="WOrmReladb.html#.delAll">delAll</a></li><li data-type='method'><a href="WOrmReladb.html#.genModelsByDB">genModelsByDB</a></li><li data-type='method'><a href="WOrmReladb.html#.genModelsByTabs">genModelsByTabs</a></li><li data-type='method'><a href="WOrmReladb.html#.genTransaction">genTransaction</a></li><li data-type='method'><a href="WOrmReladb.html#.initSequelize">initSequelize</a></li><li data-type='method'><a href="WOrmReladb.html#.insert">insert</a></li><li data-type='method'><a href="WOrmReladb.html#.insertBulk">insertBulk</a></li><li data-type='method'><a href="WOrmReladb.html#.save">save</a></li><li data-type='method'><a href="WOrmReladb.html#.select">select</a></li><li data-type='method'><a href="WOrmReladb.html#.selectByPk">selectByPk</a></li></ul></li></ul><h3>Global</h3><ul><li><a href="global.html#chkPkWf">chkPkWf</a></li><li><a href="global.html#chkWf">chkWf</a></li><li><a href="global.html#cvFind">cvFind</a></li><li><a href="global.html#genDateChecker">genDateChecker</a></li><li><a href="global.html#getColKind">getColKind</a></li><li><a href="global.html#getColScale">getColScale</a></li><li><a href="global.html#importModels">importModels</a></li><li><a href="global.html#inDateRange">inDateRange</a></li><li><a href="global.html#isKindOk">isKindOk</a></li><li><a href="global.html#normPk">normPk</a></li><li><a href="global.html#toFloating">toFloating</a></li></ul>
33
33
 
34
34
  </nav>
35
35
 
@@ -46,7 +46,10 @@
46
46
  <section>
47
47
  <article>
48
48
  <pre class="prettyprint source linenums"><code>import Sequelize from 'sequelize'
49
+ import get from 'lodash-es/get.js'
49
50
  import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
51
+ import chkOrmStr from 'wsemi/src/chkOrmStr.mjs'
52
+ import chkOrmData from 'wsemi/src/chkOrmData.mjs'
50
53
 
51
54
 
52
55
  //查詢條件(find)之轉譯, 供select與delAll共用, 令同一find於兩函數由同一SQL謂詞決定命中
@@ -60,6 +63,7 @@ import cvOrmFind from 'wsemi/src/cvOrmFind.mjs'
60
63
  //不得供破壞性之delAll使用, 1.1.7起改自寫白名單; 第33項批1起形狀層改由cvOrmFind單一擁有, 本模組刪除Symbol/Op全集、$not $nor $regex $options $exists、
61
64
  //RegExp值、陣列值即$in、非物件視為{}等形狀層(破壞性變更見CLAUDE_rulebook.md與README)
62
65
  //主鍵值之正規化(normPk)亦屬本模組, 供主鍵函數與主鍵欄條件共用, 規則見R8
66
+ //寫入側之字串良構檢核(chkPkWf、chkWf, 規則見R13)亦屬本模組: 字串之判定單一出處為wsemi之chkOrmStr、chkOrmData, 本模組只負責接線與訊息組裝
63
67
 
64
68
 
65
69
  //Op
@@ -158,6 +162,90 @@ function parseNoTzLocal(s) {
158
162
  }
159
163
 
160
164
 
165
+ /**
166
+ * 以UTC分量建構Date(牆上時間分量作為UTC瞬間, 即浮動時間), 曆法上不存在或年不在1–9999者回傳null
167
+ * 註: 以setUTCFullYear設定完整年份(Date.UTC把0–99年映射為1900–1999); 逐欄回驗, 如2026-02-30、24:00:00、60秒皆為null; 與主機時區無關(夏令跳過之時刻在浮動空間亦合法)
168
+ *
169
+ * @ignore
170
+ */
171
+ function utcOfParts(y, mo, d, h, mi, sec, ms) {
172
+ let t = new Date(0)
173
+ t.setUTCFullYear(y, mo - 1, d)
174
+ t.setUTCHours(h, mi, sec, ms)
175
+ if (t.getUTCFullYear() !== y || t.getUTCMonth() !== mo - 1 || t.getUTCDate() !== d || t.getUTCHours() !== h || t.getUTCMinutes() !== mi || t.getUTCSeconds() !== sec || t.getUTCMilliseconds() !== ms) {
176
+ return null
177
+ }
178
+ return inYearRange(y) ? t : null
179
+ }
180
+
181
+
182
+ //內建Date.prototype之getTime與本地分量getter, 供toFloating讀Date之內部時間與本地分量(載入時取得, 以call呼叫, 不受後續對原型或物件之覆寫影響)
183
+ let dateGetTime = Date.prototype.getTime
184
+ let dateGetYear = Date.prototype.getFullYear
185
+ let dateGetMonth = Date.prototype.getMonth
186
+ let dateGetDate = Date.prototype.getDate
187
+ let dateGetHours = Date.prototype.getHours
188
+ let dateGetMinutes = Date.prototype.getMinutes
189
+ let dateGetSeconds = Date.prototype.getSeconds
190
+ let dateGetMs = Date.prototype.getMilliseconds
191
+
192
+
193
+ /**
194
+ * toFloating之本體, 可能因敵意輸入(如getPrototypeOf陷阱拋錯之Proxy)而拋錯, 由toFloating統一轉為null
195
+ *
196
+ * @ignore
197
+ */
198
+ function floatOf(v) {
199
+ let inst = null
200
+ if (typeof v === 'string') {
201
+ let m = v.match(reIsoNoTz)
202
+ if (m !== null) {
203
+ let ms = m[7] === undefined ? 0 : Number(`${m[7]}00`.slice(0, 3))
204
+ return utcOfParts(Number(m[1]), Number(m[2]), Number(m[3]), Number(m[4]), Number(m[5]), m[6] === undefined ? 0 : Number(m[6]), ms)
205
+ }
206
+ let mt = v.match(reIsoTz)
207
+ if (mt === null || !isCalendarDate(mt[1], mt[2], mt[3])) {
208
+ return null
209
+ }
210
+ inst = new Date(Date.parse(v.replace(' ', 'T')))
211
+ }
212
+ else if (typeof v === 'number') {
213
+ inst = new Date(v)
214
+ }
215
+ else if (typeof v === 'object' &amp;&amp; v !== null) {
216
+ //Date以內建getTime讀其內部時間(跨realm之Date亦可; 非Date拋TypeError, 由toFloating轉為null), 並建普通Date複本取本地分量,
217
+ //不使用物件自有或原型上可被覆寫之getTime與本地getter(與條件側經cvOrmFind複製Date同, 兩側認定一致)
218
+ inst = new Date(dateGetTime.call(v))
219
+ }
220
+ if (inst === null || isNaN(dateGetTime.call(inst))) {
221
+ return null
222
+ }
223
+ return utcOfParts(dateGetYear.call(inst), dateGetMonth.call(inst) + 1, dateGetDate.call(inst), dateGetHours.call(inst), dateGetMinutes.call(inst), dateGetSeconds.call(inst), dateGetMs.call(inst))
224
+ }
225
+
226
+
227
+ /**
228
+ * 將DATE欄條件值換算為浮動時間(牆上時間分量作為UTC瞬間), 不合法者回傳null; 供ctx.dateNoTz為'floating'時條件側使用, 亦供呼叫端將後端回傳之牆上時間建為記憶體表之DATE值
229
+ * 註: 無時區字串(文法同'local': YYYY-MM-DD[T或單一空白]HH:mm[:ss[.小數]], 純日期不收)直接取其分量, 不經主機時區;
230
+ * Date、epoch與帶時區之ISO字串先得瞬間, 再取其於主機時區之本地分量(Jet等後端將本地分量視為牆上時間), 故new Date(2026,0,1)(本地午夜)恆為2026-01-01T00:00:00Z之浮動值而與主機時區無關
231
+ * 註: 契約(CLAUDE_rulebook.md之R14): ①編碼後之浮動Date/epoch不可再當'floating'條件輸入(Date一律被當作瞬間取本地分量, 再次輸入即二次位移);
232
+ * ②回傳null者呼叫端須整批拒收, 不得以原值或NULL代入(NULL使該列於範圍條件不命中、IS NULL命中, 與要修之誤刪同型);
233
+ * ③條件側走'floating'與數據側經本函數須同進同退, 混用之後果為靜默誤命中(資料遺失類)
234
+ * 註: 不拋錯(不合法者一律回傳null); 小數秒取前三位(截斷至毫秒); 牆上年須在1–9999
235
+ *
236
+ * @param {Date|Number|String} v 輸入Date、epoch數值或字串
237
+ * @returns {Date|null} 回傳浮動時間之Date, 不合法者回傳null
238
+ */
239
+ function toFloating(v) {
240
+ try {
241
+ return floatOf(v)
242
+ }
243
+ catch (e) {
244
+ return null
245
+ }
246
+ }
247
+
248
+
161
249
  /**
162
250
  * 判定年月日是否為曆法上存在之日期, 如2026-02-30不存在; 年須在1以上(年0000不收)
163
251
  * 註: 不用Date.UTC(y,..)建構, 因其把0–99年映射為1900–1999而使年0001–0099之字串被誤拒; 以setUTCFullYear設定完整年份
@@ -607,6 +695,8 @@ function unwrap(v) {
607
695
  * DECIMAL主鍵欄之值小數位多於scale者寫入時會被四捨五入而改變鍵(mssql之DECIMAL(10,2)存1.254為1.25, insert回報已存在而selectByPk(1.254)回null), 視為型別不符
608
696
  * 註: 其他型別之主鍵欄不檢型別, 收非空字串與有限數值
609
697
  * 註: NaN、±Infinity、空字串、布林、物件、陣列、Date、BigInt、null、undefined為無效
698
+ * 註: 含孤立代理碼元或NUL之字串為無效(第66項, R13; 與條件側同標準): UTF-8存放之資料庫會把孤立代理碼元改寫為U+FFFD而使不同主鍵寫成同一鍵,
699
+ * 回傳形狀不變({kind:'invalid'}), 故沿用各呼叫端對無效主鍵之既有處置; 需區辨[未給]與[不合格字串]者(autoGenPk不得以自動產生之主鍵取代呼叫端所給之不合格字串)用chkPkWf
610
700
  *
611
701
  * @param {*} v 輸入主鍵值
612
702
  * @param {String} kind 輸入主鍵欄之型別分類, 取自getColKind
@@ -623,6 +713,9 @@ function normPk(v, kind, scale = null) {
623
713
  if (v === '') {
624
714
  return { kind: 'invalid' }
625
715
  }
716
+ if (chkOrmStr(v) !== null) {
717
+ return { kind: 'invalid' }
718
+ }
626
719
  if (kind === 'int') {
627
720
  if (!reIntStd.test(v)) {
628
721
  return { kind: 'mismatch' }
@@ -662,6 +755,70 @@ function normPk(v, kind, scale = null) {
662
755
  return { kind: 'invalid' }
663
756
  }
664
757
 
758
+
759
+ /**
760
+ * 檢核主鍵值之字串良構: 字串(含new String之原始值)不得含孤立代理碼元或NUL(第66項, R13)
761
+ *
762
+ * 註: 判定單一出處為wsemi之chkOrmStr(預設連NUL一併檢查, 主鍵與條件側同標準); 非字串回傳null(其有效性由normPk判定)
763
+ * 註: 先依normPk同一規則拆包裝物件(new String), 令其與原始字串同處置
764
+ *
765
+ * @param {*} v 輸入主鍵值
766
+ * @returns {String|null} 回傳null(非字串或合格),或固定原因字串'string contains lone surrogate'、'string contains NUL'
767
+ */
768
+ function chkPkWf(v) {
769
+ v = unwrap(v)
770
+ if (typeof v !== 'string') {
771
+ return null
772
+ }
773
+ return chkOrmStr(v)
774
+ }
775
+
776
+
777
+ /**
778
+ * 逐筆檢核寫入數據之字串良構(第66項, R13), 回傳與輸入等長之訊息陣列
779
+ *
780
+ * 註: 輸入須為呼叫端數據之cloneDeep複本(檢查之值即使用之值): 本函數對各筆先檢主鍵再(withData為true時)檢資料, 同筆內主鍵先於資料,
781
+ * 資料由wsemi之chkOrmData走訪(鍵名先於值, 含巢狀物件、陣列元素、Map/Set等實存內容; 不論欄位是否在model內, 檢核先於欄位過濾)
782
+ * 註: 訊息為固定詞彙且不回顯值: invalid id: &lt;原因> at [data[&lt;索引>].&lt;主鍵欄>]、invalid data: &lt;原因> at [&lt;路徑>]; 合格者為null
783
+ * 註: 主鍵值為包裝物件(new String、new Number)且合格者, 將原始值(字串或數值)寫回複本, 令其後之正規化讀到的即本函數所檢查者(包裝物件之valueOf只求值一次)
784
+ * 註: 資料之NUL不檢查(值之NUL不拒, 僅主鍵拒NUL)
785
+ *
786
+ * @param {Array} data 輸入數據陣列(已cloneDeep)
787
+ * @param {String} pk 輸入主鍵欄位名
788
+ * @param {Boolean} withData 輸入是否一併檢核資料(false只檢主鍵, 供del)
789
+ * @returns {Array} 回傳訊息陣列,合格者為null
790
+ */
791
+ function chkWf(data, pk, withData) {
792
+ let msgs = []
793
+ for (let k = 0; k &lt; data.length; k++) {
794
+ let v = data[k]
795
+ let msg = null
796
+
797
+ //pk, 主鍵先於資料; 包裝物件之valueOf只讀取一次(u為原始值), 合格者寫回複本
798
+ let pv = get(v, pk)
799
+ let u = unwrap(pv)
800
+ let why = chkPkWf(u)
801
+ if (why !== null) {
802
+ msg = `invalid id: ${why} at [data[${k}].${pk}]`
803
+ }
804
+ else if (u !== pv &amp;&amp; (typeof u === 'string' || typeof u === 'number') &amp;&amp; v !== null &amp;&amp; typeof v === 'object') {
805
+ v[pk] = u
806
+ }
807
+
808
+ //data
809
+ if (msg === null &amp;&amp; withData) {
810
+ let r = chkOrmData(v, { root: `data[${k}]` })
811
+ if (r !== null) {
812
+ msg = `invalid data: ${r.reason} at [${r.path}]`
813
+ }
814
+ }
815
+
816
+ msgs.push(msg)
817
+ }
818
+ return msgs
819
+ }
820
+
821
+
665
822
  /**
666
823
  * 檢核純量值: 字串、有限數值、布林、有效Date, 以及null(是否允許null已由cvOrmFind依運算子判定); 並須符合欄位型別分類
667
824
  * 註: 物件、陣列等不得交sequelize之型別stringify, 否則FLOAT成'NaN'、INTEGER成'[object Object]'、DATE成本地今日00:00而誤刪;
@@ -669,6 +826,7 @@ function normPk(v, kind, scale = null) {
669
826
  * 註: 主鍵欄(pk為true)之字串類、整數類與其他數值類以normPk正規化後回傳(如字串類主鍵欄之123即'123'), 無效或型別不符者拒收,
670
827
  * 令select與delAll對主鍵之認定與selectByPk、del等主鍵函數一致; 非主鍵欄不轉型
671
828
  * 註: kind為date且呼叫端以ctx.dateNoTz明示'local'(dateNoTz為'local')時, 無時區日期時間字串依主機時區轉為Date回傳(見parseNoTzLocal), 其餘值照舊判定
829
+ * 註: kind為date且dateNoTz為'floating'時, 字串、Date、epoch一律經toFloating換算為浮動時間之Date回傳(見toFloating), 不符者拒收(R14)
672
830
  *
673
831
  * @ignore
674
832
  */
@@ -696,6 +854,14 @@ function chkScalar(v, path, kind = 'any', scale = null, pk = false, dateNoTz = u
696
854
  }
697
855
  err(`value [${String(v)}] does not match column type [${kind}]${scale !== null ? ` with scale [${scale}]` : ''}`, path)
698
856
  }
857
+ if (kind === 'date' &amp;&amp; dateNoTz === 'floating') {
858
+ //浮動時間(R14): 所有形式皆換算為牆上時間分量再以UTC分量建構(見toFloating), 與主機時區之UTC偏移無關; 不符者拒收
859
+ let t = toFloating(v)
860
+ if (t === null) {
861
+ err(`value [${String(v)}] does not match column type [${kind}]`, path)
862
+ }
863
+ return t
864
+ }
699
865
  if (kind === 'date' &amp;&amp; dateNoTz === 'local' &amp;&amp; typeof v === 'string') {
700
866
  //呼叫端明示之無時區日期時間字串: 依主機時區解析並以Date送出, 與直接給Date同義; 不符者續由isKindOk判定(帶時區者照舊)
701
867
  let t = parseNoTzLocal(v)
@@ -892,14 +1058,14 @@ function chkFindShape(find) {
892
1058
  * @param {Object} [ctx.attrs] 輸入執行where之model之rawAttributes, 供欄名解析(嚴格鍵: 鍵須與屬性名或資料庫欄名完全相符, 其餘拒收)、依欄位型別檢核純量與取得實際欄名; 未給時不解析鍵、不檢型別而交資料庫(見CLAUDE_rulebook.md之R4)
893
1059
  * @param {String} [ctx.dialect] 已不使用(原供判定sqlite之LOWER僅折疊ASCII), 保留接受以相容既有呼叫端
894
1060
  * @param {String} [ctx.pk] 輸入主鍵欄位名, 該欄之純量條件值依normPk正規化(如字串類主鍵欄之123即'123'), 未給時不正規化
895
- * @param {String} [ctx.dateNoTz] 輸入無時區日期時間字串之處置, 僅接受undefined(預設, 無時區者拒收)或'local'(依主機時區解析, 與以本地時間分量建構之Date同義), 其餘值拋錯; 僅作用於DATE欄, 供Jet等無時區後端之呼叫端明示啟用
1061
+ * @param {String} [ctx.dateNoTz] 輸入無時區日期時間字串之處置, 僅接受undefined(預設, 無時區者拒收)、'local'(依主機時區解析, 與以本地時間分量建構之Date同義)或'floating'(浮動時間: 牆上時間分量作為UTC瞬間, 見toFloating), 其餘值拋錯; 僅作用於DATE欄, 供Jet等無時區後端之呼叫端明示啟用; 'local'於負偏移時區與夏令時區有已知缺陷(見README), 此類後端請用'floating', 且數據側須同樣經toFloating
896
1062
  * @returns {Object} 回傳sequelize之where物件, 條件不合法時拋出錯誤
897
1063
  */
898
1064
  function cvFind(find, ctx) {
899
1065
 
900
- //check, ctx.dateNoTz須為undefined或'local', 其餘值(含拼錯、大小寫不同)拋錯, 不靜默忽略
901
- if (ctx &amp;&amp; ctx.dateNoTz !== undefined &amp;&amp; ctx.dateNoTz !== 'local') {
902
- throw new Error(`invalid ctx: dateNoTz must be 'local' or undefined, got [${String(ctx.dateNoTz)}]`)
1066
+ //check, ctx.dateNoTz須為undefined、'local'或'floating', 其餘值(含拼錯、大小寫不同)拋錯, 不靜默忽略
1067
+ if (ctx &amp;&amp; ctx.dateNoTz !== undefined &amp;&amp; ctx.dateNoTz !== 'local' &amp;&amp; ctx.dateNoTz !== 'floating') {
1068
+ throw new Error(`invalid ctx: dateNoTz must be 'local', 'floating' or undefined, got [${String(ctx.dateNoTz)}]`)
903
1069
  }
904
1070
 
905
1071
  //step1, 形狀檢核(R11), 其後一律只使用複本
@@ -920,7 +1086,7 @@ function cvFind(find, ctx) {
920
1086
  }
921
1087
 
922
1088
 
923
- export { getColKind, getColScale, normPk, chkFindShape, inDateRange, genDateChecker }
1089
+ export { getColKind, getColScale, normPk, chkPkWf, chkWf, chkFindShape, inDateRange, genDateChecker, toFloating }
924
1090
  export default cvFind
925
1091
  </code></pre>
926
1092
  </article>
@@ -936,7 +1102,7 @@ export default cvFind
936
1102
  <br class="clear">
937
1103
 
938
1104
  <footer>
939
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Tue Oct 20 2026 04:13:54 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1105
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Oct 21 2026 11:57:34 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
940
1106
  </footer>
941
1107
 
942
1108
  <script>prettyPrint();</script>