a collection of lightweight TypeScript packages for AT Protocol, the protocol powering Bluesky
atproto bluesky typescript npm
101
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat(tid): use high-precision system time in Node.js

Mary edf1b0ad b01eb657

+277 -14
+5
.changeset/beige-pens-like.md
··· 1 + --- 2 + '@atcute/tid': minor 3 + --- 4 + 5 + use high-precision system time in Node.js
+1 -1
.gitignore
··· 16 16 .DS_Store 17 17 18 18 .pkgsize 19 - .research 19 + .research
+2
packages/misc/time-ms/.gitignore
··· 1 + build/ 2 + prebuilds/
+7
packages/misc/time-ms/CHANGELOG.md
··· 1 + # @atcute/time-ms 2 + 3 + ## 1.0.0 4 + 5 + ### minor changes 6 + 7 + - initial release
+17
packages/misc/time-ms/README.md
··· 1 + # @atcute/time-ms 2 + 3 + high precision system time 4 + 5 + ```sh 6 + npm install @atcute/time-ms 7 + ``` 8 + 9 + ## usage 10 + 11 + ```ts 12 + import { now } from '@atcute/time-ms'; 13 + 14 + const timestamp = now(); 15 + // ^ 1766739339478426 16 + // returns microseconds since unix epoch 17 + ```
+18
packages/misc/time-ms/binding.gyp
··· 1 + { 2 + "targets": [ 3 + { 4 + "target_name": "time_ms", 5 + "sources": ["src/time_ms.c"], 6 + "cflags": ["-Wall", "-Wextra", "-O3"], 7 + "xcode_settings": { 8 + "OTHER_CFLAGS": ["-Wall", "-Wextra", "-O3"] 9 + }, 10 + "msvs_settings": { 11 + "VCCLCompilerTool": { 12 + "Optimization": 2, 13 + "WarnAsError": "false" 14 + } 15 + } 16 + } 17 + ] 18 + }
+31
packages/misc/time-ms/lib/index.node.ts
··· 1 + import { join } from 'node:path'; 2 + 3 + type TimeBinding = { 4 + now: () => number; 5 + }; 6 + 7 + let binding: TimeBinding | null = null; 8 + 9 + try { 10 + // node-gyp-build handles platform/arch detection, libc variants, etc. 11 + binding = require('node-gyp-build')(join(import.meta.dirname, '..')) as TimeBinding; 12 + } catch { 13 + binding = null; 14 + } 15 + 16 + /** 17 + * whether the native module is available for the current runtime. 18 + */ 19 + export const hasNative = binding !== null; 20 + 21 + /** 22 + * returns the current time in microseconds since unix epoch. 23 + * @returns timestamp in microseconds 24 + */ 25 + export const now = (): number => { 26 + if (binding === null) { 27 + return Date.now() * 1_000; 28 + } 29 + 30 + return binding.now(); 31 + };
+12
packages/misc/time-ms/lib/index.ts
··· 1 + /** 2 + * whether the native module is available for the current runtime. 3 + */ 4 + export const hasNative = false; 5 + 6 + /** 7 + * returns the current time in microseconds since unix epoch. 8 + * @returns timestamp in microseconds 9 + */ 10 + export const now = (): number => { 11 + return Date.now() * 1_000; 12 + };
+45
packages/misc/time-ms/package.json
··· 1 + { 2 + "type": "module", 3 + "name": "@atcute/time-ms", 4 + "version": "1.0.0", 5 + "description": "high precision system time helper", 6 + "license": "0BSD", 7 + "repository": { 8 + "url": "https://github.com/mary-ext/atcute", 9 + "directory": "packages/utilities/time-ms" 10 + }, 11 + "publishConfig": { 12 + "access": "public" 13 + }, 14 + "files": [ 15 + "dist/", 16 + "lib/", 17 + "prebuilds/", 18 + "src/", 19 + "binding.gyp", 20 + "!lib/**/*.bench.ts", 21 + "!lib/**/*.test.ts" 22 + ], 23 + "exports": { 24 + ".": { 25 + "node": "./dist/index.node.js", 26 + "default": "./dist/index.js" 27 + } 28 + }, 29 + "sideEffects": false, 30 + "scripts": { 31 + "install": "node-gyp-build", 32 + "build:native": "prebuildify --napi --strip", 33 + "build": "tsgo --project tsconfig.build.json", 34 + "test": "vitest", 35 + "prepublish": "rm -rf dist; pnpm run build:native; pnpm run build" 36 + }, 37 + "devDependencies": { 38 + "prebuildify": "^6.0.1", 39 + "vitest": "^4.0.16" 40 + }, 41 + "dependencies": { 42 + "@types/node": "^22.19.3", 43 + "node-gyp-build": "^4.8.4" 44 + } 45 + }
+38
packages/misc/time-ms/src/time_ms.c
··· 1 + #include <node_api.h> 2 + #include <stdint.h> 3 + 4 + #ifdef _WIN32 5 + #include <windows.h> 6 + #else 7 + #include <time.h> 8 + #endif 9 + 10 + static napi_value now(napi_env env, napi_callback_info info) { 11 + int64_t microseconds; 12 + 13 + #ifdef _WIN32 14 + FILETIME ft; 15 + ULARGE_INTEGER ul; 16 + GetSystemTimePreciseAsFileTime(&ft); 17 + ul.LowPart = ft.dwLowDateTime; 18 + ul.HighPart = ft.dwHighDateTime; 19 + // convert from 100-nanosecond intervals since 1601 to microseconds since 1970 20 + microseconds = (int64_t)((ul.QuadPart - 116444736000000000ULL) / 10); 21 + #else 22 + struct timespec ts; 23 + clock_gettime(CLOCK_REALTIME, &ts); 24 + microseconds = (int64_t)ts.tv_sec * 1000000 + ts.tv_nsec / 1000; 25 + #endif 26 + 27 + napi_value result; 28 + napi_create_int64(env, microseconds, &result); 29 + return result; 30 + } 31 + 32 + static napi_value init(napi_env env, napi_value exports) { 33 + napi_property_descriptor desc = { "now", NULL, now, NULL, NULL, NULL, napi_default, NULL }; 34 + napi_define_properties(env, exports, 1, &desc); 35 + return exports; 36 + } 37 + 38 + NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
+4
packages/misc/time-ms/tsconfig.build.json
··· 1 + { 2 + "extends": "./tsconfig.json", 3 + "exclude": ["**/*.test.ts"] 4 + }
+24
packages/misc/time-ms/tsconfig.json
··· 1 + { 2 + "compilerOptions": { 3 + "types": ["node"], 4 + "outDir": "dist/", 5 + "esModuleInterop": true, 6 + "skipLibCheck": true, 7 + "target": "ESNext", 8 + "allowJs": true, 9 + "resolveJsonModule": true, 10 + "moduleDetection": "force", 11 + "isolatedModules": true, 12 + "verbatimModuleSyntax": true, 13 + "strict": true, 14 + "noImplicitOverride": true, 15 + "noUnusedLocals": true, 16 + "noUnusedParameters": true, 17 + "noFallthroughCasesInSwitch": true, 18 + "module": "NodeNext", 19 + "sourceMap": true, 20 + "declaration": true, 21 + "declarationMap": true, 22 + }, 23 + "include": ["lib"], 24 + }
+14 -9
packages/utilities/tid/lib/index.ts
··· 1 + import { now as getNow } from '@atcute/time-ms'; 1 2 import { s32decode, s32encode } from './s32.js'; 2 3 3 - let lastTimestamp: number = 0; 4 + let lastTimestamp = 0; 5 + let lastCurrentTime = 0; 4 6 5 7 const TID_RE = /^[234567abcdefghij][234567abcdefghijklmnopqrstuvwxyz]{12}$/; 6 8 ··· 30 32 * Return a TID based on current time 31 33 */ 32 34 export const now = (): string => { 33 - // we need these two aspects, which Date.now() doesn't provide: 34 - // - monotonically increasing time 35 - // - microsecond precision 35 + const currentTime = getNow(); 36 + let timestamp: number; 36 37 37 - // while `performance.timeOrigin + performance.now()` could be used here, they 38 - // seem to have cross-browser differences, not sure on that yet. 38 + if (currentTime === lastCurrentTime) { 39 + // same time; increment to avoid collision 40 + timestamp = lastTimestamp + 1; 41 + } else { 42 + // time changed 43 + timestamp = currentTime; 44 + lastCurrentTime = currentTime; 45 + } 39 46 40 - let timestamp = Math.max(Date.now() * 1_000, lastTimestamp); 41 - lastTimestamp = timestamp + 1; 42 - 47 + lastTimestamp = timestamp; 43 48 return createRaw(timestamp, Math.floor(Math.random() * 1023)); 44 49 }; 45 50
+3
packages/utilities/tid/package.json
··· 30 30 "test": "vitest", 31 31 "prepublish": "rm -rf dist; pnpm run build" 32 32 }, 33 + "dependencies": { 34 + "@atcute/time-ms": "workspace:^" 35 + }, 33 36 "devDependencies": { 34 37 "vitest": "^4.0.16" 35 38 }
+56 -4
pnpm-lock.yaml
··· 454 454 '@atcute/uint8array': 455 455 specifier: workspace:^ 456 456 version: link:../../misc/uint8array 457 + '@atcute/util-fetch': 458 + specifier: workspace:^ 459 + version: link:../../misc/util-fetch 457 460 '@badrap/valita': 458 461 specifier: ^0.4.6 459 462 version: 0.4.6 ··· 697 700 vitest: 698 701 specifier: ^4.0.16 699 702 version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0) 703 + 704 + packages/misc/time-ms: 705 + dependencies: 706 + '@types/node': 707 + specifier: ^22.19.3 708 + version: 22.19.3 709 + node-gyp-build: 710 + specifier: ^4.8.4 711 + version: 4.8.4 712 + devDependencies: 713 + prebuildify: 714 + specifier: ^6.0.1 715 + version: 6.0.1 716 + vitest: 717 + specifier: ^4.0.16 718 + version: 4.0.16(@types/node@22.19.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0) 700 719 701 720 packages/misc/uint8array: 702 721 devDependencies: ··· 1100 1119 version: 4.0.16(@types/node@25.0.3)(@vitest/browser-playwright@4.0.16)(jiti@2.6.1)(tsx@4.20.6)(yaml@2.8.0) 1101 1120 1102 1121 packages/utilities/tid: 1122 + dependencies: 1123 + '@atcute/time-ms': 1124 + specifier: workspace:^ 1125 + version: link:../../misc/time-ms 1103 1126 devDependencies: 1104 1127 vitest: 1105 1128 specifier: ^4.0.16 ··· 3593 3616 resolution: {integrity: sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==} 3594 3617 hasBin: true 3595 3618 3619 + node-gyp-build@4.8.4: 3620 + resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} 3621 + hasBin: true 3622 + 3596 3623 nodemailer-html-to-text@3.2.0: 3597 3624 resolution: {integrity: sha512-RJUC6640QV1PzTHHapOrc6IzrAJUZtk2BdVdINZ9VTLm+mcQNyBO9LYyhrnufkzqiD9l8hPLJ97rSyK4WanPNg==} 3598 3625 engines: {node: '>= 10.23.0'} ··· 3601 3628 resolution: {integrity: sha512-Z+iLaBGVaSjbIzQ4pX6XV41HrooLsQ10ZWPUehGmuantvzWoDVBnmsdUcOIDM1t+yPor5pDhVlDESgOMEGxhHA==} 3602 3629 engines: {node: '>=6.0.0'} 3603 3630 3631 + npm-run-path@3.1.0: 3632 + resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==} 3633 + engines: {node: '>=8'} 3634 + 3604 3635 object-assign@4.1.1: 3605 3636 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 3606 3637 engines: {node: '>=0.10.0'} ··· 3825 3856 prebuild-install@7.1.3: 3826 3857 resolution: {integrity: sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==} 3827 3858 engines: {node: '>=10'} 3859 + hasBin: true 3860 + 3861 + prebuildify@6.0.1: 3862 + resolution: {integrity: sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==} 3828 3863 hasBin: true 3829 3864 3830 3865 prettier@2.8.8: ··· 6435 6470 6436 6471 '@types/bn.js@5.2.0': 6437 6472 dependencies: 6438 - '@types/node': 25.0.3 6473 + '@types/node': 22.19.3 6439 6474 6440 6475 '@types/bun@1.3.5': 6441 6476 dependencies: ··· 6467 6502 '@types/node@25.0.3': 6468 6503 dependencies: 6469 6504 undici-types: 7.16.0 6505 + optional: true 6470 6506 6471 6507 '@types/ws@8.18.1': 6472 6508 dependencies: 6473 - '@types/node': 25.0.3 6509 + '@types/node': 22.19.3 6474 6510 6475 6511 '@typescript/native-preview-darwin-arm64@7.0.0-dev.20251221.1': 6476 6512 optional: true ··· 6787 6823 6788 6824 bun-types@1.3.5: 6789 6825 dependencies: 6790 - '@types/node': 25.0.3 6826 + '@types/node': 22.19.3 6791 6827 6792 6828 bytes@3.1.2: {} 6793 6829 ··· 7562 7598 detect-libc: 2.1.2 7563 7599 optional: true 7564 7600 7601 + node-gyp-build@4.8.4: {} 7602 + 7565 7603 nodemailer-html-to-text@3.2.0: 7566 7604 dependencies: 7567 7605 html-to-text: 7.1.1 7568 7606 7569 7607 nodemailer@6.10.1: {} 7608 + 7609 + npm-run-path@3.1.0: 7610 + dependencies: 7611 + path-key: 3.1.1 7570 7612 7571 7613 object-assign@4.1.1: {} 7572 7614 ··· 7797 7839 tar-fs: 2.1.4 7798 7840 tunnel-agent: 0.6.0 7799 7841 7842 + prebuildify@6.0.1: 7843 + dependencies: 7844 + minimist: 1.2.8 7845 + mkdirp-classic: 0.5.3 7846 + node-abi: 3.85.0 7847 + npm-run-path: 3.1.0 7848 + pump: 3.0.3 7849 + tar-fs: 2.1.4 7850 + 7800 7851 prettier@2.8.8: {} 7801 7852 7802 7853 prettier@3.7.4: {} ··· 8213 8264 8214 8265 undici-types@6.21.0: {} 8215 8266 8216 - undici-types@7.16.0: {} 8267 + undici-types@7.16.0: 8268 + optional: true 8217 8269 8218 8270 undici@6.22.0: {} 8219 8271