meadow 2.0.39 → 2.0.40

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.
@@ -0,0 +1,18 @@
1
+ {
2
+ "Hash": "meadow",
3
+ "Name": "Meadow",
4
+ "Tagline": "Provider-agnostic data broker with CRUD operations, audit tracking, and soft deletes across 8 database engines",
5
+ "Palette": "mix",
6
+ "Icon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\" width=\"96\" height=\"96\">\n\t\t<defs>\n\t\t\t<clipPath id=\"frame-meadow-filled-light\">\n\t\t\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\"/>\n\t\t\t</clipPath>\n\t\t</defs>\n\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\" fill=\"#31cae2\"/>\n\t\t<g clip-path=\"url(#frame-meadow-filled-light)\"><path d=\"M 32.75 15.39 A 36 36 0 0 1 63.25 80.61 Z\" fill=\"rgba(255,255,255,0.18)\" opacity=\"0.95\"/>\n\t\t\t\t\t<path d=\"M 32.75 15.39 A 36 36 0 0 0 63.25 80.61 Z\" fill=\"#dd9166\" opacity=\"0.95\"/></g>\n\t\t<text x=\"48\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\"\n\t\t\tfont-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\"\n\t\t\tfont-size=\"56\" font-weight=\"600\"\n\t\t\tfill=\"#ffffff\" letter-spacing=\"-1\">M</text>\n\t</svg>",
7
+ "IconType": "svg",
8
+ "Favicon": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\" width=\"96\" height=\"96\">\n\t\t<defs>\n\t\t\t<clipPath id=\"fav-meadow-light\">\n\t\t\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\"/>\n\t\t\t</clipPath>\n\t\t</defs>\n\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\" fill=\"#31cae2\"/>\n\t\t<g clip-path=\"url(#fav-meadow-light)\"><circle cx=\"48\" cy=\"48\" r=\"40\" fill=\"rgba(255,255,255,0.22)\"/></g>\n\t\t<text x=\"48\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\"\n\t\t\tfont-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\"\n\t\t\tfont-size=\"60\" font-weight=\"800\"\n\t\t\tfill=\"#ffffff\" letter-spacing=\"-1\">M</text>\n\t</svg>",
9
+ "FaviconDark": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 96 96\" width=\"96\" height=\"96\">\n\t\t<defs>\n\t\t\t<clipPath id=\"fav-meadow-dark\">\n\t\t\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\"/>\n\t\t\t</clipPath>\n\t\t</defs>\n\t\t<path d=\"M 24 2\n\t\tH 72\n\t\tQ 94 2 94 24\n\t\tV 72\n\t\tQ 94 94 72 94\n\t\tH 24\n\t\tQ 2 94 2 72\n\t\tV 24\n\t\tQ 2 2 24 2 Z\" fill=\"#86dce9\"/>\n\t\t<g clip-path=\"url(#fav-meadow-dark)\"><circle cx=\"48\" cy=\"48\" r=\"40\" fill=\"rgba(255,255,255,0.22)\"/></g>\n\t\t<text x=\"48\" y=\"50\" text-anchor=\"middle\" dominant-baseline=\"central\"\n\t\t\tfont-family=\"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif\"\n\t\t\tfont-size=\"60\" font-weight=\"800\"\n\t\t\tfill=\"#101418\" letter-spacing=\"-1\">M</text>\n\t</svg>",
10
+ "Colors": {
11
+ "Primary": "#31cae2",
12
+ "Secondary": "#dd9166",
13
+ "PrimaryLight": "#31cae2",
14
+ "PrimaryDark": "#86dce9",
15
+ "SecondaryLight": "#dd9166",
16
+ "SecondaryDark": "#e9c4af"
17
+ }
18
+ }
package/docs/index.html CHANGED
@@ -8,8 +8,6 @@
8
8
 
9
9
  <title>Meadow v2.0.33 Documentation</title>
10
10
 
11
- <!-- Application Stylesheet -->
12
- <link href="css/docuserve.css" rel="stylesheet">
13
11
  <!-- KaTeX stylesheet for LaTeX equation rendering -->
14
12
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
15
13
  <!-- PICT Dynamic View CSS Container -->
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meadow",
3
- "version": "2.0.39",
3
+ "version": "2.0.40",
4
4
  "description": "A data access library.",
5
5
  "main": "source/Meadow.js",
6
6
  "scripts": {
@@ -95,7 +95,7 @@
95
95
  "meadow-connection-sqlite-browser": "^1.0.2",
96
96
  "mongodb": "^6.12.0",
97
97
  "mysql2": "^3.18.2",
98
- "pict-docuserve": "^1.0.0",
98
+ "pict-docuserve": "^1.0.1",
99
99
  "puppeteer": "^24.38.0",
100
100
  "quackage": "^1.2.3",
101
101
  "solr-client": "^0.9.0"
@@ -45,9 +45,13 @@ var meadowBehaviorCreate = function(pMeadow, pQuery, fCallBack)
45
45
  {
46
46
  pMeadow.fable.log.warn('Error during the unique-constraint pre-flight',
47
47
  { Error: pPreflightError, Message: pPreflightError && pPreflightError.message });
48
- return fStageComplete(pPreflightError, pQuery, false);
48
+ return setImmediate(fStageComplete, pPreflightError, pQuery, false);
49
49
  }
50
- return fStageComplete();
50
+ // Defer via setImmediate so synchronous providers (node:sqlite,
51
+ // previously better-sqlite3) cannot stack-accumulate frames
52
+ // across a long synchronous chain of Creates — every record's
53
+ // pre-flight returns to the event loop before the INSERT runs.
54
+ return setImmediate(fStageComplete);
51
55
  });
52
56
  },
53
57
  // Step 1: Create the record in the data source
@@ -4,6 +4,8 @@
4
4
  * @author <steven@velozo.com>
5
5
  */
6
6
 
7
+ var libAsyncEachSeries = require('async/eachSeries');
8
+
7
9
  var MeadowProvider = function()
8
10
  {
9
11
  function createNew(pFable)
@@ -436,7 +438,25 @@ var MeadowProvider = function()
436
438
  * Data: [] (optional array of records, one object each)
437
439
  * }
438
440
  */
439
- var constructFromObject = (pParameters) =>
441
+ /**
442
+ * Construct an ALASQL-backed meadow from a JS object array.
443
+ *
444
+ * Signatures:
445
+ * constructFromObject(pParameters) — bindObject path only;
446
+ * returns meadow synchronously
447
+ * constructFromObject(pParameters, fCallback) — Import path (doCreate
448
+ * per record); meadow + records
449
+ * delivered via fCallback
450
+ *
451
+ * The Import path drives `doCreate` per record, which is no longer
452
+ * guaranteed to complete in the same tick (Meadow-Create's pre-flight
453
+ * yields via setImmediate to keep bulk-create chains stack-safe on
454
+ * synchronous providers). Callers that need the imported records
455
+ * queryable on return must pass a callback. Without a callback the
456
+ * Import path is rejected — synchronous fire-and-forget imports were
457
+ * a latent footgun and are no longer supported.
458
+ */
459
+ var constructFromObject = (pParameters, fCallback) =>
440
460
  {
441
461
  if ((typeof(pParameters) !== 'object') || (typeof(pParameters.Meadow) !== 'object'))
442
462
  return false;
@@ -452,12 +472,12 @@ var MeadowProvider = function()
452
472
  pParameters.Import = true;
453
473
  if (!Array.isArray(pParameters.Data))
454
474
  pParameters.Data = [];
455
-
475
+
456
476
  // Construct a meadow
457
477
  var tmpMeadow = pParameters.Meadow
458
478
  .new(_Fable, pParameters.Scope)
459
479
  .setProvider('ALASQL');
460
-
480
+
461
481
  var tmpSchema = [];
462
482
  var tmpDefaultIdentifier;
463
483
 
@@ -489,7 +509,7 @@ var MeadowProvider = function()
489
509
  case "function":
490
510
  // Do nothing with these types of properties
491
511
  break;
492
-
512
+
493
513
  case "boolean":
494
514
  tmpSchema.push({ Column: tmpProperty, Type:"Boolean" });
495
515
  break;
@@ -499,7 +519,7 @@ var MeadowProvider = function()
499
519
  case "string":
500
520
  tmpSchema.push({ Column: tmpProperty, Type:"Text" });
501
521
  break;
502
-
522
+
503
523
  default:
504
524
  break;
505
525
  }
@@ -516,23 +536,47 @@ var MeadowProvider = function()
516
536
  // Now import the data
517
537
  if(pParameters.Import)
518
538
  {
519
- for (var j = 0; j < pParameters.Data.length; j++)
539
+ if (typeof(fCallback) !== 'function')
520
540
  {
521
- tmpMeadow.doCreate(tmpMeadow.query.clone().addRecord(pParameters.Data[j]),
522
- function(pError, pQuery, pQueryRead, pRecord)
523
- {
524
- // Maybe log the error?
525
- _Fable.log.trace('Auto imported record', pRecord);
526
- }
527
- );
541
+ _Fable.log.error('Meadow-Provider-ALASQL: constructFromObject called with Import=true but no callback — doCreate is no longer synchronous, callers must await record import.');
542
+ return false;
528
543
  }
544
+
545
+ libAsyncEachSeries(
546
+ pParameters.Data,
547
+ function (pRecord, fNext)
548
+ {
549
+ tmpMeadow.doCreate(tmpMeadow.query.clone().addRecord(pRecord),
550
+ function (pError, pQuery, pQueryRead, pCreated)
551
+ {
552
+ if (pError)
553
+ {
554
+ _Fable.log.warn('Meadow-Provider-ALASQL: auto-import doCreate failed', { Error: pError, Record: pRecord });
555
+ }
556
+ else
557
+ {
558
+ _Fable.log.trace('Auto imported record', pCreated);
559
+ }
560
+ // Errors are logged but not propagated — the historical
561
+ // fire-and-forget loop swallowed them, preserve that
562
+ // shape so partial imports still return a usable meadow.
563
+ return fNext();
564
+ });
565
+ },
566
+ function ()
567
+ {
568
+ return fCallback(null, tmpMeadow);
569
+ });
570
+ return tmpMeadow;
529
571
  }
530
- else
572
+
573
+ // Just assign the object
574
+ tmpMeadow.provider.bindObject(pParameters.Data);
575
+
576
+ if (typeof(fCallback) === 'function')
531
577
  {
532
- // Just assign the object
533
- tmpMeadow.provider.bindObject(pParameters.Data);
578
+ fCallback(null, tmpMeadow);
534
579
  }
535
-
536
580
  return tmpMeadow;
537
581
  };
538
582
 
@@ -105,6 +105,54 @@ var MeadowProvider = function ()
105
105
  return pParams;
106
106
  };
107
107
 
108
+ /**
109
+ * Filter a named-parameters object down to only those keys whose
110
+ * `:name` form appears in the SQL body.
111
+ *
112
+ * node:sqlite is strict about unknown named parameters — passing
113
+ * a key the prepared statement doesn't reference throws
114
+ * `Unknown named parameter '<name>'`. better-sqlite3 was lenient
115
+ * about extras, so this regressed silently when
116
+ * meadow-connection-sqlite switched to node:sqlite. It matters
117
+ * whenever Meadow.rawQueries.loadQuery installs an override SQL
118
+ * whose placeholders are a strict subset of meadow's auto-
119
+ * generated params (filter columns, soft-delete tracking,
120
+ * AutoGUID conflict-check), but the filter is cheap and applied
121
+ * uniformly — also protects any future case where the auto-built
122
+ * SQL and the params dict drift apart.
123
+ *
124
+ * Approach mirrors `convertNamedToPositional` in the PostgreSQL
125
+ * provider: walk the SQL with /:([A-Za-z_][A-Za-z0-9_]*)/g, keep
126
+ * names that exist in the params dict, leave the literal alone
127
+ * otherwise (handles `::typecast`-style sequences without
128
+ * tripping). Over-matches inside string literals are harmless
129
+ * because we only ever *keep* params — never invent them.
130
+ */
131
+ var filterUsedNamedParams = function (pQueryBody, pNamedParams)
132
+ {
133
+ if (typeof (pNamedParams) !== 'object' || pNamedParams === null)
134
+ {
135
+ return pNamedParams;
136
+ }
137
+ if (typeof (pQueryBody) !== 'string')
138
+ {
139
+ return pNamedParams;
140
+ }
141
+
142
+ var tmpUsed = {};
143
+ var tmpPattern = /:([A-Za-z_][A-Za-z0-9_]*)/g;
144
+ var tmpMatch;
145
+ while ((tmpMatch = tmpPattern.exec(pQueryBody)) !== null)
146
+ {
147
+ var tmpName = tmpMatch[1];
148
+ if (Object.prototype.hasOwnProperty.call(pNamedParams, tmpName))
149
+ {
150
+ tmpUsed[tmpName] = pNamedParams[tmpName];
151
+ }
152
+ }
153
+ return tmpUsed;
154
+ };
155
+
108
156
  // The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
109
157
  var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
110
158
  {
@@ -173,6 +221,11 @@ var MeadowProvider = function ()
173
221
 
174
222
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
175
223
  coerceParameters(pQuery.query.parameters);
224
+ // Filter to keys actually referenced as :name in the SQL —
225
+ // node:sqlite is strict about unknown named parameters, and
226
+ // meadow auto-generates filter/Deleted/AutoGUID params that
227
+ // a rawQueries override may not reference.
228
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
176
229
 
177
230
  if (pQuery.logLevel > 0)
178
231
  {
@@ -190,7 +243,7 @@ var MeadowProvider = function ()
190
243
  }
191
244
 
192
245
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
193
- var tmpInfo = tmpStatement.run(pQuery.query.parameters);
246
+ var tmpInfo = tmpStatement.run(tmpBindParams);
194
247
 
195
248
  tmpResult.error = null;
196
249
  tmpResult.value = false;
@@ -222,6 +275,11 @@ var MeadowProvider = function ()
222
275
 
223
276
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
224
277
  coerceParameters(pQuery.query.parameters);
278
+ // Filter to keys actually referenced as :name in the SQL —
279
+ // node:sqlite is strict about unknown named parameters, and
280
+ // meadow auto-generates filter/Deleted/AutoGUID params that
281
+ // a rawQueries override may not reference.
282
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
225
283
 
226
284
  if (pQuery.logLevel > 0)
227
285
  {
@@ -239,7 +297,7 @@ var MeadowProvider = function ()
239
297
  }
240
298
 
241
299
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
242
- var tmpRows = tmpStatement.all(pQuery.query.parameters);
300
+ var tmpRows = tmpStatement.all(tmpBindParams);
243
301
 
244
302
  tmpResult.error = null;
245
303
  tmpResult.value = tmpRows;
@@ -262,6 +320,11 @@ var MeadowProvider = function ()
262
320
 
263
321
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
264
322
  coerceParameters(pQuery.query.parameters);
323
+ // Filter to keys actually referenced as :name in the SQL —
324
+ // node:sqlite is strict about unknown named parameters, and
325
+ // meadow auto-generates filter/Deleted/AutoGUID params that
326
+ // a rawQueries override may not reference.
327
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
265
328
 
266
329
  if (pQuery.logLevel > 0)
267
330
  {
@@ -279,7 +342,7 @@ var MeadowProvider = function ()
279
342
  }
280
343
 
281
344
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
282
- var tmpInfo = tmpStatement.run(pQuery.query.parameters);
345
+ var tmpInfo = tmpStatement.run(tmpBindParams);
283
346
 
284
347
  tmpResult.error = null;
285
348
  tmpResult.value = tmpInfo;
@@ -302,6 +365,11 @@ var MeadowProvider = function ()
302
365
 
303
366
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
304
367
  coerceParameters(pQuery.query.parameters);
368
+ // Filter to keys actually referenced as :name in the SQL —
369
+ // node:sqlite is strict about unknown named parameters, and
370
+ // meadow auto-generates filter/Deleted/AutoGUID params that
371
+ // a rawQueries override may not reference.
372
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
305
373
 
306
374
  if (pQuery.logLevel > 0)
307
375
  {
@@ -319,7 +387,7 @@ var MeadowProvider = function ()
319
387
  }
320
388
 
321
389
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
322
- var tmpInfo = tmpStatement.run(pQuery.query.parameters);
390
+ var tmpInfo = tmpStatement.run(tmpBindParams);
323
391
 
324
392
  tmpResult.error = null;
325
393
  tmpResult.value = false;
@@ -350,6 +418,11 @@ var MeadowProvider = function ()
350
418
 
351
419
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
352
420
  coerceParameters(pQuery.query.parameters);
421
+ // Filter to keys actually referenced as :name in the SQL —
422
+ // node:sqlite is strict about unknown named parameters, and
423
+ // meadow auto-generates filter/Deleted/AutoGUID params that
424
+ // a rawQueries override may not reference.
425
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
353
426
 
354
427
  if (pQuery.logLevel > 0)
355
428
  {
@@ -367,7 +440,7 @@ var MeadowProvider = function ()
367
440
  }
368
441
 
369
442
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
370
- var tmpInfo = tmpStatement.run(pQuery.query.parameters);
443
+ var tmpInfo = tmpStatement.run(tmpBindParams);
371
444
 
372
445
  tmpResult.error = null;
373
446
  tmpResult.value = false;
@@ -398,6 +471,11 @@ var MeadowProvider = function ()
398
471
 
399
472
  var tmpQueryBody = fixDateFunctions(pQuery.query.body);
400
473
  coerceParameters(pQuery.query.parameters);
474
+ // Filter to keys actually referenced as :name in the SQL —
475
+ // node:sqlite is strict about unknown named parameters, and
476
+ // meadow auto-generates filter/Deleted/AutoGUID params that
477
+ // a rawQueries override may not reference.
478
+ var tmpBindParams = filterUsedNamedParams(tmpQueryBody, pQuery.query.parameters);
401
479
 
402
480
  if (pQuery.logLevel > 0)
403
481
  {
@@ -415,7 +493,7 @@ var MeadowProvider = function ()
415
493
  }
416
494
 
417
495
  var tmpStatement = tmpDB.prepare(tmpQueryBody);
418
- var tmpRows = tmpStatement.all(pQuery.query.parameters);
496
+ var tmpRows = tmpStatement.all(tmpBindParams);
419
497
 
420
498
  tmpResult.executed = true;
421
499
  tmpResult.error = null;
@@ -968,26 +968,35 @@ suite
968
968
  {Brand:'Puma', Style:'Lowtop', Size:15, SKU:'dsa33234'}
969
969
  ];
970
970
 
971
- var testMeadow = require('../source/Meadow.js')
972
- .new(libFable, 'Master')
973
- .setProvider('ALASQL')
974
- .provider.constructFromObject(
975
- {
976
- Meadow: require('../source/Meadow.js').new(libFable, 'Master'),
977
- Scope: 'ShoeCity',
978
- ObjectPrototype: myData[0],
979
- Data: myData
980
- });
981
-
982
- testMeadow.doReads(testMeadow.query.clone().addFilter('SKU', 'dsa33234'),
983
- function(pError, pQuery, pRecords)
984
- {
985
- // We should have a record ....
986
- Expect(pRecords[0].Style)
987
- .to.equal('Lowtop');
988
- fDone();
989
- }
990
- );
971
+ // constructFromObject's Import path drives doCreate per record,
972
+ // which is no longer synchronous (Meadow-Create defers its
973
+ // pre-flight callback via setImmediate to keep bulk-create
974
+ // chains stack-safe on sync providers). Pass a callback so
975
+ // records are queryable before the subsequent doReads runs.
976
+ require('../source/Meadow.js')
977
+ .new(libFable, 'Master')
978
+ .setProvider('ALASQL')
979
+ .provider.constructFromObject(
980
+ {
981
+ Meadow: require('../source/Meadow.js').new(libFable, 'Master'),
982
+ Scope: 'ShoeCity',
983
+ ObjectPrototype: myData[0],
984
+ Data: myData
985
+ },
986
+ function (pImportError, testMeadow)
987
+ {
988
+ Expect(pImportError, pImportError && pImportError.message).to.not.exist;
989
+
990
+ testMeadow.doReads(testMeadow.query.clone().addFilter('SKU', 'dsa33234'),
991
+ function(pError, pQuery, pRecords)
992
+ {
993
+ // We should have a record ....
994
+ Expect(pRecords[0].Style)
995
+ .to.equal('Lowtop');
996
+ fDone();
997
+ }
998
+ );
999
+ });
991
1000
  }
992
1001
  );
993
1002
  }
@@ -744,8 +744,11 @@ suite
744
744
  testMeadow.doCount(testMeadow.query.setScope('BadTable'),
745
745
  function(pError, pQuery, pRecord)
746
746
  {
747
+ // chai's type-detect classifies Error instances
748
+ // as 'error', not 'object' — assert against the
749
+ // type chai actually returns.
747
750
  Expect(pError)
748
- .to.be.an('object');
751
+ .to.be.an('error');
749
752
  fDone();
750
753
  }
751
754
  )
@@ -1080,5 +1083,244 @@ suite
1080
1083
  );
1081
1084
  }
1082
1085
  );
1086
+ suite
1087
+ (
1088
+ 'Bulk Create — synchronous chain stack safety',
1089
+ function ()
1090
+ {
1091
+ // Regression for the stack-overflow seen during liveconnect_local
1092
+ // bulk sync: every doCreate runs the libRenameSoftDeletedConflicts
1093
+ // pre-flight, which on a sync provider (node:sqlite) returns its
1094
+ // callback in the same tick. Combined with a synchronous caller
1095
+ // chain (eachLimit's replenish loop, or a tail-recursive driver
1096
+ // like this test), per-record frames accumulate without bound.
1097
+ // The fix is a setImmediate barrier at the pre-flight callback
1098
+ // in Meadow-Create.js so each Create yields to the event loop
1099
+ // before the INSERT runs.
1100
+ var _StackTestSchema =
1101
+ [
1102
+ { Column: 'IDStackTest', Type: 'AutoIdentity' },
1103
+ { Column: 'GUIDStackTest', Type: 'AutoGUID' },
1104
+ { Column: 'CreateDate', Type: 'CreateDate' },
1105
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
1106
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
1107
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
1108
+ { Column: 'Deleted', Type: 'Deleted' },
1109
+ { Column: 'DeleteDate', Type: 'DeleteDate' },
1110
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
1111
+ { Column: 'Code', Type: 'String', Unique: true },
1112
+ { Column: 'Name', Type: 'String' }
1113
+ ];
1114
+ var _StackTestDefault =
1115
+ {
1116
+ IDStackTest: 0,
1117
+ GUIDStackTest: '',
1118
+ CreateDate: false,
1119
+ CreatingIDUser: 0,
1120
+ UpdateDate: false,
1121
+ UpdatingIDUser: 0,
1122
+ Deleted: 0,
1123
+ DeleteDate: false,
1124
+ DeletingIDUser: 0,
1125
+ Code: '',
1126
+ Name: ''
1127
+ };
1128
+
1129
+ var newStackTestMeadow = function ()
1130
+ {
1131
+ return require('../source/Meadow.js').new(libFable, 'StackTest')
1132
+ .setProvider('SQLite')
1133
+ .setSchema(_StackTestSchema)
1134
+ .setJsonSchema({ title: 'StackTest', type: 'object', properties: {}, required: [] })
1135
+ .setDefaultIdentifier('IDStackTest')
1136
+ .setDefault(_StackTestDefault);
1137
+ };
1138
+
1139
+ suiteSetup(function (fDone)
1140
+ {
1141
+ var tmpDB = libFable.MeadowSQLiteProvider.db;
1142
+ tmpDB.exec(
1143
+ 'CREATE TABLE IF NOT EXISTS StackTest (' +
1144
+ ' IDStackTest INTEGER PRIMARY KEY AUTOINCREMENT,' +
1145
+ ' GUIDStackTest TEXT NOT NULL DEFAULT \'\',' +
1146
+ ' CreateDate TEXT,' +
1147
+ ' CreatingIDUser INTEGER NOT NULL DEFAULT 0,' +
1148
+ ' UpdateDate TEXT,' +
1149
+ ' UpdatingIDUser INTEGER NOT NULL DEFAULT 0,' +
1150
+ ' Deleted INTEGER NOT NULL DEFAULT 0,' +
1151
+ ' DeleteDate TEXT,' +
1152
+ ' DeletingIDUser INTEGER NOT NULL DEFAULT 0,' +
1153
+ ' Code TEXT NOT NULL DEFAULT \'\',' +
1154
+ ' Name TEXT NOT NULL DEFAULT \'\'' +
1155
+ ');' +
1156
+ 'CREATE UNIQUE INDEX IF NOT EXISTS stacktest_code_unique ON StackTest(Code);'
1157
+ );
1158
+ return fDone();
1159
+ });
1160
+
1161
+ test
1162
+ (
1163
+ '1500 sync-chained doCreate calls complete without RangeError',
1164
+ function (fDone)
1165
+ {
1166
+ // 1500 was chosen empirically: with a single Unique column
1167
+ // pre-flight (one synchronous Read per Create) plus the
1168
+ // surrounding waterfall steps, per-record frame growth
1169
+ // without the fix exceeds V8's default stack budget well
1170
+ // before reaching this count. With the fix in place each
1171
+ // iteration yields to the event loop, so frame growth is
1172
+ // bounded and the chain completes in O(seconds).
1173
+ this.timeout(30000);
1174
+
1175
+ var tmpRecordCount = 1500;
1176
+ var tmpCreated = 0;
1177
+ var tmpFirstError = null;
1178
+
1179
+ var fCreateNext = function (pIndex)
1180
+ {
1181
+ if (pIndex >= tmpRecordCount)
1182
+ {
1183
+ Expect(tmpFirstError, tmpFirstError && tmpFirstError.message).to.not.exist;
1184
+ Expect(tmpCreated).to.equal(tmpRecordCount);
1185
+ return fDone();
1186
+ }
1187
+
1188
+ var tmpMeadow = newStackTestMeadow().setIDUser(1);
1189
+ var tmpQuery = tmpMeadow.query.addRecord(
1190
+ {
1191
+ Code: 'stack-' + pIndex,
1192
+ Name: 'row-' + pIndex
1193
+ });
1194
+
1195
+ // Caller invokes the next iteration synchronously
1196
+ // inside doCreate's terminal callback — the exact
1197
+ // pattern that surfaced the bug. Without the
1198
+ // setImmediate barrier inside Meadow-Create, this
1199
+ // chain stack-overflows in node:sqlite mid-loop.
1200
+ tmpMeadow.doCreate(tmpQuery, function (pError)
1201
+ {
1202
+ if (pError && !tmpFirstError)
1203
+ {
1204
+ tmpFirstError = pError;
1205
+ }
1206
+ else if (!pError)
1207
+ {
1208
+ tmpCreated++;
1209
+ }
1210
+ return fCreateNext(pIndex + 1);
1211
+ });
1212
+ };
1213
+
1214
+ fCreateNext(0);
1215
+ }
1216
+ );
1217
+ }
1218
+ );
1219
+ suite
1220
+ (
1221
+ 'Raw query overrides — parameter handling',
1222
+ function ()
1223
+ {
1224
+ // EXPECTED FAILING (until provider fix lands).
1225
+ //
1226
+ // When rawQueries.loadQuery installs a Read override that
1227
+ // doesn't reference some of the named parameters meadow's
1228
+ // query builder auto-generates (e.g. the soft-delete filter
1229
+ // `Deleted_w1`, or a user-added filter like `IDWidget_w0`),
1230
+ // the SQLite provider still binds them via
1231
+ // `statement.all(pQuery.query.parameters)`. node:sqlite is
1232
+ // strict about unknown named parameters and throws
1233
+ // `Unknown named parameter '<name>'` — better-sqlite3 was
1234
+ // lenient about extras, so this regressed silently when
1235
+ // meadow-connection-sqlite switched to node:sqlite.
1236
+ //
1237
+ // Isolation: dedicated table + raw-query file with no
1238
+ // placeholders, so the test doesn't depend on FableTest
1239
+ // state mutated by earlier suites.
1240
+ var _WidgetSchema =
1241
+ [
1242
+ { Column: 'IDWidget', Type: 'AutoIdentity' },
1243
+ { Column: 'GUIDWidget', Type: 'AutoGUID' },
1244
+ { Column: 'CreateDate', Type: 'CreateDate' },
1245
+ { Column: 'CreatingIDUser', Type: 'CreateIDUser' },
1246
+ { Column: 'UpdateDate', Type: 'UpdateDate' },
1247
+ { Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
1248
+ { Column: 'Deleted', Type: 'Deleted' },
1249
+ { Column: 'DeleteDate', Type: 'DeleteDate' },
1250
+ { Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
1251
+ { Column: 'Label', Type: 'String' }
1252
+ ];
1253
+
1254
+ var newWidgetMeadow = function ()
1255
+ {
1256
+ return require('../source/Meadow.js').new(libFable, 'Widget')
1257
+ .setProvider('SQLite')
1258
+ .setSchema(_WidgetSchema)
1259
+ .setJsonSchema({ title: 'Widget', type: 'object', properties: {}, required: [] })
1260
+ .setDefaultIdentifier('IDWidget');
1261
+ };
1262
+
1263
+ var _RawQueryPath = __dirname + '/Meadow-Provider-SQLite-WidgetRawRead.sql';
1264
+
1265
+ suiteSetup(function (fDone)
1266
+ {
1267
+ var tmpDB = libFable.MeadowSQLiteProvider.db;
1268
+ tmpDB.exec(
1269
+ 'CREATE TABLE IF NOT EXISTS Widget (' +
1270
+ ' IDWidget INTEGER PRIMARY KEY AUTOINCREMENT,' +
1271
+ ' GUIDWidget TEXT NOT NULL DEFAULT \'\',' +
1272
+ ' CreateDate TEXT,' +
1273
+ ' CreatingIDUser INTEGER NOT NULL DEFAULT 0,' +
1274
+ ' UpdateDate TEXT,' +
1275
+ ' UpdatingIDUser INTEGER NOT NULL DEFAULT 0,' +
1276
+ ' Deleted INTEGER NOT NULL DEFAULT 0,' +
1277
+ ' DeleteDate TEXT,' +
1278
+ ' DeletingIDUser INTEGER NOT NULL DEFAULT 0,' +
1279
+ ' Label TEXT NOT NULL DEFAULT \'\'' +
1280
+ ');'
1281
+ );
1282
+ tmpDB.exec("INSERT INTO Widget (Label) VALUES ('alpha'), ('beta'), ('gamma');");
1283
+
1284
+ // Raw query has no placeholders — projection-only SELECT.
1285
+ libFS.writeFileSync(_RawQueryPath,
1286
+ 'SELECT IDWidget, Label AS LabelAlias FROM Widget WHERE IDWidget = 1');
1287
+ return fDone();
1288
+ });
1289
+
1290
+ suiteTeardown(function (fDone)
1291
+ {
1292
+ try { libFS.unlinkSync(_RawQueryPath); } catch (pError) { /* ignore */ }
1293
+ return fDone();
1294
+ });
1295
+
1296
+ test
1297
+ (
1298
+ 'doRead with a meadow-generated filter still resolves a parameterless raw query',
1299
+ function (fDone)
1300
+ {
1301
+ var testMeadow = newWidgetMeadow();
1302
+
1303
+ testMeadow.rawQueries.loadQuery('Read', _RawQueryPath, function (pLoaded)
1304
+ {
1305
+ Expect(pLoaded).to.equal(true);
1306
+
1307
+ // addFilter generates a `:IDWidget_w0` named parameter
1308
+ // (and soft-delete tracking adds `:Deleted_w1`).
1309
+ // Neither appears in the raw SQL above. Without
1310
+ // param-filtering in the provider, node:sqlite
1311
+ // throws "Unknown named parameter 'IDWidget_w0'".
1312
+ testMeadow.doRead(testMeadow.query.addFilter('IDWidget', 1),
1313
+ function (pError, pQuery, pRecord)
1314
+ {
1315
+ Expect(pError, pError && pError.message ? pError.message : pError).to.not.exist;
1316
+ Expect(pRecord, 'pRecord should be a marshalled object, not ' + pRecord).to.be.an('object');
1317
+ Expect(pRecord.LabelAlias).to.equal('alpha');
1318
+ fDone();
1319
+ });
1320
+ });
1321
+ }
1322
+ );
1323
+ }
1324
+ );
1083
1325
  }
1084
1326
  );
@@ -1,327 +0,0 @@
1
- /* ============================================================================
2
- Pict Docuserve - Base Styles & Theme Variables
3
- ============================================================================ */
4
-
5
- /* ----------------------------------------------------------------------------
6
- Theme variables — light defaults on :root.
7
- Dark mode applies when either:
8
- (a) the user explicitly selected dark via <html data-theme="dark">
9
- (b) the user hasn't chosen anything AND the system prefers dark
10
- An explicit <html data-theme="light"> pins the light palette regardless.
11
- ---------------------------------------------------------------------------- */
12
-
13
- :root
14
- {
15
- /* Surfaces */
16
- --docuserve-bg: #FDFBF7;
17
- --docuserve-bg-elevated: #FFFFFF;
18
- --docuserve-border: #DDD6CA;
19
- --docuserve-border-soft: #EAE3D8;
20
-
21
- /* Text */
22
- --docuserve-text: #2A241E;
23
- --docuserve-text-strong: #3D3229;
24
- --docuserve-text-muted: #5E5549;
25
- --docuserve-text-dim: #8A7F72;
26
-
27
- /* Accent / links */
28
- --docuserve-accent: #2E7D74;
29
- --docuserve-accent-hover: #236660;
30
-
31
- /* Top bar */
32
- --docuserve-topbar-bg: #3D3229;
33
- --docuserve-topbar-text: #E8E0D4;
34
- --docuserve-topbar-text-muted: #B5AA9A;
35
- --docuserve-topbar-text-dim: #8A7F72;
36
- --docuserve-topbar-hover-bg: #524438;
37
- --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
38
- --docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
39
- --docuserve-topbar-version-text: #B5AA9A;
40
-
41
- /* Sidebar */
42
- --docuserve-sidebar-bg: #FAF7F1;
43
- --docuserve-sidebar-border: #DDD6CA;
44
- --docuserve-sidebar-border-soft: #E5DED1;
45
- --docuserve-sidebar-text: #423D37;
46
- --docuserve-sidebar-group-title: #3D3229;
47
- --docuserve-sidebar-module-text: #5E5549;
48
- --docuserve-sidebar-hover-bg: #EAE3D8;
49
- --docuserve-sidebar-hover-text: #2E7D74;
50
- --docuserve-sidebar-active-bg: #E5DED1;
51
- --docuserve-sidebar-active-text: #2E7D74;
52
- --docuserve-sidebar-search-bg: #FFFFFF;
53
- --docuserve-sidebar-search-border: #DDD6CA;
54
-
55
- /* Inline code */
56
- --docuserve-inline-code-bg: #F0ECE4;
57
- --docuserve-inline-code-text: #9E3A50;
58
-
59
- /* Code block panel */
60
- --docuserve-code-bg: #F6F3EE;
61
- --docuserve-code-border: #E5DED1;
62
- --docuserve-code-gutter-bg: #EFEAE0;
63
- --docuserve-code-gutter-border: #DDD6CA;
64
- --docuserve-code-gutter-text: #A59986;
65
- --docuserve-code-text: #2A241E;
66
-
67
- /* Syntax tokens — low-chroma dark-on-light palette */
68
- --docuserve-tok-keyword: #A03472;
69
- --docuserve-tok-string: #1A6640;
70
- --docuserve-tok-number: #B25A00;
71
- --docuserve-tok-comment: #8A7F72;
72
- --docuserve-tok-operator: #2E7D74;
73
- --docuserve-tok-punctuation: #2A241E;
74
- --docuserve-tok-function: #2A5DB0;
75
- --docuserve-tok-property: #9E3A50;
76
- --docuserve-tok-tag: #9E3A50;
77
- --docuserve-tok-attr-name: #B25A00;
78
- --docuserve-tok-attr-value: #1A6640;
79
-
80
- /* Tables, blockquotes, mermaid */
81
- --docuserve-table-header-bg: #F5F0E8;
82
- --docuserve-table-row-alt-bg: #F9F6F0;
83
- --docuserve-blockquote-bg: #F7F5F0;
84
- --docuserve-blockquote-border: #2E7D74;
85
- --docuserve-blockquote-text: #5E5549;
86
- --docuserve-mermaid-bg: #FFFFFF;
87
-
88
- /* Scrollbars */
89
- --docuserve-scrollbar-track: #F5F0E8;
90
- --docuserve-scrollbar-thumb: #D4CCBE;
91
- --docuserve-scrollbar-thumb-hover: #B5AA9A;
92
- }
93
-
94
- @media (prefers-color-scheme: dark)
95
- {
96
- :root:not([data-theme="light"])
97
- {
98
- --docuserve-bg: #15120F;
99
- --docuserve-bg-elevated: #1B1814;
100
- --docuserve-border: #2F2823;
101
- --docuserve-border-soft: #26211C;
102
-
103
- --docuserve-text: #E8E0D4;
104
- --docuserve-text-strong: #F2ECE0;
105
- --docuserve-text-muted: #B5AA9A;
106
- --docuserve-text-dim: #7A6F62;
107
-
108
- --docuserve-accent: #5DB8A8;
109
- --docuserve-accent-hover: #7FCCB8;
110
-
111
- --docuserve-topbar-bg: #1A1612;
112
- --docuserve-topbar-text: #E8E0D4;
113
- --docuserve-topbar-text-muted: #B5AA9A;
114
- --docuserve-topbar-text-dim: #7A6F62;
115
- --docuserve-topbar-hover-bg: #2A241E;
116
- --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
117
- --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
118
- --docuserve-topbar-version-text: #B5AA9A;
119
-
120
- --docuserve-sidebar-bg: #1B1814;
121
- --docuserve-sidebar-border: #2F2823;
122
- --docuserve-sidebar-border-soft: #26211C;
123
- --docuserve-sidebar-text: #C9C0B3;
124
- --docuserve-sidebar-group-title: #F2ECE0;
125
- --docuserve-sidebar-module-text: #B5AA9A;
126
- --docuserve-sidebar-hover-bg: #2A241E;
127
- --docuserve-sidebar-hover-text: #7FCCB8;
128
- --docuserve-sidebar-active-bg: #2F2823;
129
- --docuserve-sidebar-active-text: #7FCCB8;
130
- --docuserve-sidebar-search-bg: #26211C;
131
- --docuserve-sidebar-search-border: #2F2823;
132
-
133
- --docuserve-inline-code-bg: #2A241E;
134
- --docuserve-inline-code-text: #E8B07A;
135
-
136
- --docuserve-code-bg: #1E1A17;
137
- --docuserve-code-border: #2F2823;
138
- --docuserve-code-gutter-bg: #17130F;
139
- --docuserve-code-gutter-border: #2F2823;
140
- --docuserve-code-gutter-text: #6A6052;
141
- --docuserve-code-text: #E8E0D4;
142
-
143
- --docuserve-tok-keyword: #C678DD;
144
- --docuserve-tok-string: #98C379;
145
- --docuserve-tok-number: #D19A66;
146
- --docuserve-tok-comment: #7F848E;
147
- --docuserve-tok-operator: #56B6C2;
148
- --docuserve-tok-punctuation: #E8E0D4;
149
- --docuserve-tok-function: #61AFEF;
150
- --docuserve-tok-property: #E06C75;
151
- --docuserve-tok-tag: #E06C75;
152
- --docuserve-tok-attr-name: #D19A66;
153
- --docuserve-tok-attr-value: #98C379;
154
-
155
- --docuserve-table-header-bg: #26211C;
156
- --docuserve-table-row-alt-bg: #1F1B17;
157
- --docuserve-blockquote-bg: #1F1B17;
158
- --docuserve-blockquote-border: #5DB8A8;
159
- --docuserve-blockquote-text: #C9C0B3;
160
- --docuserve-mermaid-bg: #E8E0D4;
161
-
162
- --docuserve-scrollbar-track: #1B1814;
163
- --docuserve-scrollbar-thumb: #3A322B;
164
- --docuserve-scrollbar-thumb-hover: #524438;
165
- }
166
- }
167
-
168
- :root[data-theme="dark"]
169
- {
170
- --docuserve-bg: #15120F;
171
- --docuserve-bg-elevated: #1B1814;
172
- --docuserve-border: #2F2823;
173
- --docuserve-border-soft: #26211C;
174
-
175
- --docuserve-text: #E8E0D4;
176
- --docuserve-text-strong: #F2ECE0;
177
- --docuserve-text-muted: #B5AA9A;
178
- --docuserve-text-dim: #7A6F62;
179
-
180
- --docuserve-accent: #5DB8A8;
181
- --docuserve-accent-hover: #7FCCB8;
182
-
183
- --docuserve-topbar-bg: #1A1612;
184
- --docuserve-topbar-text: #E8E0D4;
185
- --docuserve-topbar-text-muted: #B5AA9A;
186
- --docuserve-topbar-text-dim: #7A6F62;
187
- --docuserve-topbar-hover-bg: #2A241E;
188
- --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
189
- --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
190
- --docuserve-topbar-version-text: #B5AA9A;
191
-
192
- --docuserve-sidebar-bg: #1B1814;
193
- --docuserve-sidebar-border: #2F2823;
194
- --docuserve-sidebar-border-soft: #26211C;
195
- --docuserve-sidebar-text: #C9C0B3;
196
- --docuserve-sidebar-group-title: #F2ECE0;
197
- --docuserve-sidebar-module-text: #B5AA9A;
198
- --docuserve-sidebar-hover-bg: #2A241E;
199
- --docuserve-sidebar-hover-text: #7FCCB8;
200
- --docuserve-sidebar-active-bg: #2F2823;
201
- --docuserve-sidebar-active-text: #7FCCB8;
202
- --docuserve-sidebar-search-bg: #26211C;
203
- --docuserve-sidebar-search-border: #2F2823;
204
-
205
- --docuserve-inline-code-bg: #2A241E;
206
- --docuserve-inline-code-text: #E8B07A;
207
-
208
- --docuserve-code-bg: #1E1A17;
209
- --docuserve-code-border: #2F2823;
210
- --docuserve-code-gutter-bg: #17130F;
211
- --docuserve-code-gutter-border: #2F2823;
212
- --docuserve-code-gutter-text: #6A6052;
213
- --docuserve-code-text: #E8E0D4;
214
-
215
- --docuserve-tok-keyword: #C678DD;
216
- --docuserve-tok-string: #98C379;
217
- --docuserve-tok-number: #D19A66;
218
- --docuserve-tok-comment: #7F848E;
219
- --docuserve-tok-operator: #56B6C2;
220
- --docuserve-tok-punctuation: #E8E0D4;
221
- --docuserve-tok-function: #61AFEF;
222
- --docuserve-tok-property: #E06C75;
223
- --docuserve-tok-tag: #E06C75;
224
- --docuserve-tok-attr-name: #D19A66;
225
- --docuserve-tok-attr-value: #98C379;
226
-
227
- --docuserve-table-header-bg: #26211C;
228
- --docuserve-table-row-alt-bg: #1F1B17;
229
- --docuserve-blockquote-bg: #1F1B17;
230
- --docuserve-blockquote-border: #5DB8A8;
231
- --docuserve-blockquote-text: #C9C0B3;
232
- --docuserve-mermaid-bg: #E8E0D4;
233
-
234
- --docuserve-scrollbar-track: #1B1814;
235
- --docuserve-scrollbar-thumb: #3A322B;
236
- --docuserve-scrollbar-thumb-hover: #524438;
237
- }
238
-
239
- /* ----------------------------------------------------------------------------
240
- Reset and base
241
- ---------------------------------------------------------------------------- */
242
-
243
- *, *::before, *::after
244
- {
245
- box-sizing: border-box;
246
- }
247
-
248
- html, body
249
- {
250
- margin: 0;
251
- padding: 0;
252
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
253
- font-size: 16px;
254
- line-height: 1.5;
255
- color: var(--docuserve-text);
256
- background-color: var(--docuserve-bg);
257
- -webkit-font-smoothing: antialiased;
258
- -moz-osx-font-smoothing: grayscale;
259
- transition: background-color 0.15s ease, color 0.15s ease;
260
- }
261
-
262
- /* Typography */
263
- h1, h2, h3, h4, h5, h6
264
- {
265
- margin-top: 0;
266
- line-height: 1.3;
267
- color: var(--docuserve-text-strong);
268
- }
269
-
270
- a
271
- {
272
- color: var(--docuserve-accent);
273
- text-decoration: none;
274
- }
275
-
276
- a:hover
277
- {
278
- color: var(--docuserve-accent-hover);
279
- }
280
-
281
- /* Application container */
282
- #Docuserve-Application-Container
283
- {
284
- min-height: 100vh;
285
- }
286
-
287
- /* Utility: scrollbar styling */
288
- ::-webkit-scrollbar
289
- {
290
- width: 8px;
291
- height: 8px;
292
- }
293
-
294
- ::-webkit-scrollbar-track
295
- {
296
- background: var(--docuserve-scrollbar-track);
297
- }
298
-
299
- ::-webkit-scrollbar-thumb
300
- {
301
- background: var(--docuserve-scrollbar-thumb);
302
- border-radius: 4px;
303
- }
304
-
305
- ::-webkit-scrollbar-thumb:hover
306
- {
307
- background: var(--docuserve-scrollbar-thumb-hover);
308
- }
309
-
310
- /* Responsive adjustments */
311
- @media (max-width: 768px)
312
- {
313
- html
314
- {
315
- font-size: 14px;
316
- }
317
-
318
- #Docuserve-Sidebar-Container
319
- {
320
- display: none;
321
- }
322
-
323
- .docuserve-body
324
- {
325
- flex-direction: column;
326
- }
327
- }